LeetCode剑指Offer刷题总结(五)

x33g5p2x  于2021-11-16 转载在 其他  
字(4.9k)|赞(0)|评价(0)|浏览(217)

题号为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;
    }
}

相关文章