1. 程式人生 > 其它 >LeetCode劍指Offer刷題總結(五)

LeetCode劍指Offer刷題總結(五)

題號為LeetCode劍指Offer題庫中的題號。網址:https://leetcode-cn.com/problem-list/xb9nqhhg/

1~n 整數中 1 出現的次數 43

  • 限制:1 <= n < 2^31

數字範圍較大,暴力會超時

class Solution {
    public int countDigitOne(int n) {
        int sum = 0;
        int cur = n%10, digit = 1, high = n/10 , low = 0;
        while(cur != 0 || high != 0){
            if(cur==0) sum += high*digit;
            else if(cur == 1) sum+= high*digit + low + 1;
            else sum+= high*digit + digit;
            low = cur*digit + low;
            cur = high %10;
            high = high /10;
            digit *=10;
        }
        return sum;
    }
}

本題的思路很精巧,可以試著思考行李箱上的密碼鎖,確定每個數位為1的情況有多少種。

當前位為0,意味著假如說 2301,則只有0010-2219的數字是符合條件的,這裡面有229+1個情況。

當前位為1,假如說 2314,則只有0010-2314是符合條件的,234+1個情況。

當前位大於1,假如說2334,則有0010-2319是符合條件的,239+1個情況。

數字序列中某一位的數字 44

這裡的思想並不麻煩,但是要注意每個數位的處理過程,很可能少減一次1,結果就截然不同了。

class Solution {
    public int findNthDigit(int n) {
        int digit = 1;
        long start = 1;
        long count = 9;
        while (n > count) { // 1.
            n -= count;
            digit += 1;
            start *= 10;
            count = digit * start * 9;
        }
        long num = start + (n - 1) / digit; // 2.
        return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.
    }
}

2,3步中的-1需要仔細理解。

把陣列排成最小的數 45(快排)

class Solution {
    public String minNumber(int[] nums) {
        String[] res1 = new String[nums.length];
        for(int i = 0 ; i<nums.length ; i++) {
            res1[i] = String.valueOf(nums[i]);
        }
        Arrays.sort(res1, (x,y)->(x+y).compareTo(y+x));
        StringBuilder res = new StringBuilder();
        for(String s : res1)
            res.append(s);
        return res.toString();
    }
}

該題其實就是一個快速排序,只是需要將判斷條件由(x,y)->(x-y)升序 改為 (x,y)-> (x+y).compareTo(y+x) (該內容為字串比較)。該方式為lamda表示式。

class Solution {
    public String minNumber(int[] nums) {
        String[] res1 = new String[nums.length];
        for(int i = 0 ; i<nums.length ; i++) {
            res1[i] = String.valueOf(nums[i]);
        }
        quickSort(res1,0,res1.length-1);
        StringBuilder res = new StringBuilder();
        for(String s : res1)
            res.append(s);
        return res.toString();
    }

    public void quickSort(String[] str, int l, int r) {
        if(l>=r) return ;
        int i = l , j = r;
        String tmp = str[l];
        while(i<j) {
            while( (str[j]+str[l]).compareTo(str[l]+str[j])>=0 && i<j ) j--;
            while( (str[i]+str[l]).compareTo(str[l]+str[i])<=0 && i<j ) i++;
            String tmp1 = str[j];
            str[j] = str[i];
            str[i] = tmp1;
        }
        str[l] = str[j];
        str[j] = tmp;
        quickSort(str,l,j-1);
        quickSort(str,j+1,r);
    }
}

快排完整版

把數字翻譯成字串 46

class Solution {

    int count = 0;

    public int translateNum(int num) {
        dfs(String.valueOf(num));
        return count;
    }

    public void dfs(String s) {
        if(s.length() <= 1) {
            count++;
            return;
        }
        int len = s.length();
        if(s.substring(0,2).compareTo("25") <= 0 && s.charAt(0)!='0'){
            dfs(s.substring(2));
            dfs(s.substring(1));
        }
        else
            dfs(s.substring(1));
    }
}

這題的思路也是比較簡單,DFS即可。樹形遞迴的時間複雜度在O(2^N)

動態規劃解法:

public int translateNum(int num) {
        String s = String.valueOf(num);
        int a = 1, b = 1;
        for(int i = 2; i <= s.length(); i++) {
            String tmp = s.substring(i - 2, i);
            int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
            b = a;
            a = c;
        }
        return a;
    }

a為dp0,b為dp1。

禮物的最大價值 47

典型的動態規劃

每次狀態為向上一步的最優值或者向左一步最優值的最大者。

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for(int i = 0 ; i < m ; i++)
            for(int j = 0 ; j < n ; j++) {
                if(i==0 && j==0) continue;
                else if(i==0) grid[i][j]+=grid[i][j-1];
                else if(j==0) grid[i][j]+=grid[i-1][j];
                else{
                    grid[i][j] = Math.max(grid[i][j]+grid[i][j-1], grid[i][j]+grid[i-1][j]);
                }
            }
        return grid[m-1][n-1];
    }
}

最長不含重複字元的子字串 48

動態規劃,其中利用了hash表標記元素位置

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> map = new HashMap<>();
        int tmp = 0, res = 0;
        for(int i = 0 ; i < s.length() ; i++) {
            int j = map.getOrDefault(s.charAt(i),-1);
            map.put(s.charAt(i),i);
            tmp = tmp < i-j ? tmp+1 : i-j;
            res = res > tmp ? res : tmp;
        }
        return res;
    }
}

假設字串abcab,則迴圈到a時,檢測到0位置上為重複元素,判斷tmp為3,i-j即當前元素距重複元素距離3,則說明子串中含有a,更新為i-j。

醜數 49

class Solution {
    public int nthUglyNumber(int n) {
        int[] factor = {2,3,5};
        int ans=0;
        Set<Long> set =  new HashSet<>() {{add(1L);}};
        PriorityQueue<Long> res = new PriorityQueue<>() {{offer(1L);}};
        for(int i = 1 ; i <= n ; i++) {
            long ugly = res.poll();
            ans = (int) ugly;
            for(int j : factor){
                long next = ugly*j;
                if(set.add(next))
                    res.offer(next);
            }
        }
        return ans;
    }
}

注意,題目只要求包含質因子2,3,5,因此必須用每個醜數再乘2,3,5獲取新的醜數,並判斷不重複,PriorityQueue不會自動判別重複元素。

第一個只出現一次的字元 50

利用雜湊表遍歷

class Solution {
    public char firstUniqChar(String s) {
        char res =' ';
        if(s.length()==0)
            return ' ';
        Map<Character, Integer> map = new HashMap<>();
        for(int i = 0 ; i < s.length() ; i++) {
            if(map.getOrDefault(s.charAt(i),-1)==-1)
                map.put(s.charAt(i),0);
            else map.put(s.charAt(i),1);
        }
        for(int i = 0 ; i < s.length() ; i++) {
            if(map.get(s.charAt(i))==0) {
                res = s.charAt(i);
                break;
            }
        }
        return res;
    }
}

陣列中的逆序對 51 (歸併變形)

class Solution {
    int[] tmp;
    public int reversePairs(int[] nums) {
        int len = nums.length;
        tmp = new int[len];
        return mergeSort(nums,0,len-1);
    }

    public int mergeSort(int[] nums, int l, int r) {
        if(l>=r)
            return 0;
        int m = (l+r)/2, i = l, j = m+1;
        int res = mergeSort(nums,l,m) + mergeSort(nums,m+1,r);
        for(int k = l ; k <= r ; k++) {
            tmp[k] = nums[k];
        }
        for(int k = l ; k <= r ; k++) {
            if(i == m+1){
                nums[k] = tmp[j++];
            }
            else if( j == r+1 || tmp[i]<=tmp[j]) {
                nums[k] = tmp[i++];
            }
            else {
                nums[k] = tmp[j++];
                res+= m-i+1;
            }
        }
        return res;
    }
}

在歸併的每一步合併中,進行逆序對計數。注意各個臨界點的判斷。

兩個連結串列的第一個公共節點 52

利用雜湊表遍歷,要善於利用hashset和map,但是要注意.equal的實現機制。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        while(headA!=null){
            set.add(headA);
            headA = headA.next;
        }
        while(headB!=null){
            if(!set.add(headB))
                break;
            headB = headB.next;
        }
        return headB;
    }
}