剑指Offer41—50题(leetcode 剑指Offer第二版)

41,数字序列中某一位的数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKI73ch0-1652325292359)(%E5%89%91%E6%8C%87Offer.assets/1651569062318.png)]

思路分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Om7pqPR-1652325292360)(%E5%89%91%E6%8C%87Offer.assets/1651569116746.png)]

代码实现:

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution35
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/5/3 16:36
 */
public class Solution35 {
    public int findNthDigit(int n) {
        //表示n所在位置的数的位数
        int digit = 1;
        //表示位数的数字的第一个数
        long start = 1;
        //表示这个范围中数的占位符
        long count = 9;
        while (n > count) {
            n -= count;
            digit++;
            start *= 10;
            count = digit * start * 9;
        }

        long num = start + (n - 1)/digit;
        return Long.toString(num).charAt((n - 1) % digit) - '0';
    }
}

42,把数组排成最小的数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODC47CCE-1652325292360)(%E5%89%91%E6%8C%87Offer.assets/1651579355190.png)]

思路分析;

就是要将其按照一个特定的排序方式进行排序,之后再进行拼接。

因为最后是要进行拼接的,所以我们不能按照简单的大小进行排序,所以我们必须要将其进行拼接后进行比较,即要将前后字符进行拼接后比较,而这种比较的方式只能通过自己创建一个比较器来完成。

代码实现:

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution36
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/5/3 19:38
 */
public class Solution36 {
    public static String minNumber(int[] nums) {
        StringBuilder res = new StringBuilder();
        PriorityQueue<String> queue = new PriorityQueue<>(new MinString());
        for (int num : nums) {
            queue.add(String.valueOf(num));
        }
        while (!queue.isEmpty()) {
            res.append(queue.poll());
        }
        return res.toString();
    }

    public static class MinString implements Comparator<String> {
        @Override
        public int compare(String o1, String o2) {
            return (o1 + o2).compareTo(o2 + o1);
        }
    }

    public static void main(String[] args) {
        int[] arr = {10,2};
        System.out.println(minNumber(arr));
    }
}

43,把数字翻译成字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjSEONxC-1652325292361)(%E5%89%91%E6%8C%87Offer.assets/1651585628359.png)]

代码实现

首先先考虑递归是如何实现的,进一步去修改成动态规划。

递归的代码如下:

public static int translateNum(int num) {
    String a = String.valueOf(num);
    char[] arr = a.toCharArray();
    if (arr.length == 0 || arr == null) {
        return 0;
    }
    return process(arr, 0);
}

public static int process(char[] arr, int index) {
    //basecase 当我走完这个arr后就说明已经有一种解释
    if (index == arr.length) {
        return 1;
    }
    //如果chs[i]是1,就有两种分配方式,一种就是与后一位结合,之后继续进行,一种是直接进行。
    if (arr[index] == '1') {
        int res = process(arr, index + 1);
        if (index + 1 < arr.length) {
            res += process(arr, index + 2);
        }
        return res;
        //如果chs[i]是2,就有两种情况,如果数字是在0—5,就有两种分配方式,如果是6—9,就是直接继续分配
    }else if (arr[index] == '2') {
        int res = process(arr, index + 1);
        if (index + 1 < arr.length && (arr[index + 1] >= '0' && arr[index + 1] <= '5')) {
            res += process(arr, index + 2);
        }
        return res;
    }
    return process(arr, index + 1);
}

由上面的代码可以知,我们只有一个变量是index,所以我们的dp只用创建一个一位数组即可。

这里先假设num = 12258。将其变成字符串

我们要求的是dp[5]的位置。而且如果只有num的长度为1或者0时,那么dp的长度只能是1.也就是

dp[0]和dp[1]是1.

然后开始找每个位置的关系,通过上面递归的代码可以知道,如果是0到25之间,我们是与前两个位置有关的,且是相加。如果是别的结果,直接用前一个结果。

然后写这个动态规划的代码

class Solution {
    public int translateNum(int num) {
        String a = String.valueOf(num);
        int[] res = new int[a.length() + 10];
        if (a.length() == 0 || a == null) {
            return 0;
        }
        res[0] = res[1] = 1;
        for (int i = 2; i <= a.length(); i++) {
            if ((i + 1) <= a.length()) {

            }
            String temp = a.substring(i - 2,i);
            if (temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0) {
                res[i] = res[i-1] + res[i-2];
            }else {
                res[i] = res[i - 1];
            }

        }
        return res[a.length()];
    }
}

44,礼物的最大价值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aB4MrneI-1652325292361)(%E5%89%91%E6%8C%87Offer.assets/1651885695133.png)]

思路分析:

他是让我们在一个二维数组中选择一条路,使得从左上角到右下角的值是最大的,且限制条件是每一步只能是向下走或者向右走。

对于暴力递归而言,刚开始的这一步,我们要么向下走,要么向右走,所以分为两个方法体。选择一个最大的。对于每个方法而言,他们都有自己的限制条件,向下走的话,当到达最后一层时,只能向右走。向右走的话,到达最右边只能向下走。

public class Solution38 {
    public int maxValue(int[][] grid) {
        return Math.max(getDown(grid,0,0),getRight(grid,0,0));
    }

    public int getDown(int[][] grid,int x,int y){
        //终止条件:走到终点 base case
        if(x==grid.length-1&&y==grid[0].length-1)return grid[x][y];
        //到了下边界,那么就只能往右走
        if(x==grid.length-1)return grid[x][y] + getRight(grid,x,y+1);  
        //至于别的情况,就是当前位置加上下一个位置向右或者向下的最大值。
        return grid[x][y] + Math.max(getDown(grid,x+1,y),getRight(grid,x+1,y));
    }

    public int getRight(int[][] grid,int x,int y){
        //终止条件:走到终点
        if(x==grid.length-1&&y==grid[0].length-1)return grid[x][y];
        //到了有边界,只能往下走
        if(y==grid[0].length-1)return grid[x][y] + getDown(grid,x+1,y); 
        return grid[x][y] + Math.max(getDown(grid,x,y+1),getRight(grid,x,y+1));
    }

}

动态规划的话可以通过上面的代码推到出来的。首先我们有两个变量,分别是x和y。所以我们创建一个二维数组。同时我们也知道我们的basecase是走到右下角的节点。而且对于第一行位置的最大值只能是我们一直向右走,对于第一列的最大值只能是一直向下走。别的节点都是由我们左边或者上边的最大节点加上该节点。所以动态规划代码就得到了。

class Solution {
    public int maxValue(int[][] grid) {
        int row = grid.length;
        int column = grid[0].length;
        int[][] dp = new int[grid.length + 5][grid[0].length + 5];
        dp[0][0] = grid[0][0];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                if (i == 0 && j == 0) {
                    continue;
                }
                if (i == 0) {
                    dp[i][j] = dp[i][j - 1] + grid[i][j];
                    continue;
                }
                if (j == 0) {
                    dp[i][j] = dp[i - 1][j] + grid[i][j];
                    continue;
                }
                dp[i][j] = Math.max(dp[i - 1][j] + grid[i][j], dp[i][j - 1] + grid[i][j]);
            }
        }
        return dp[row - 1][column - 1];
    }
}

45,最长不含重复字符的子字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SxiFKXGt-1652325292361)(%E5%89%91%E6%8C%87Offer.assets/1652014160346.png)]

思路分析

就是利用两个指针和一个存储的map集合,一个往前走,一个停留在原地。将每走一步的字符放入到map中,如果map中有两个该字符时,可以让后面的往前走,直到这个到达相同的该字符为止。

代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int res = 0;

        HashMap<Character, Integer> hashMap = new HashMap<>();
        for (int i = 0,l = 0; i < s.length(); i++) {
            if (hashMap.containsKey(s.charAt(i))) {
                Integer integer = hashMap.get(s.charAt(i));
                hashMap.put(s.charAt(i), integer + 1);

            }else {
                hashMap.put(s.charAt(i), 1);
            }
            while (hashMap.get(s.charAt(i)) > 1) {
                //在中途中,经过的节点要让其减一
                Integer integer = hashMap.get(s.charAt(l));
                hashMap.put(s.charAt(l), integer - 1);
                l++;
                //System.out.println(l);
            }
            res = Math.max(res, i - l + 1);
        }
        return res;
    }
}

46,丑数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUQiFdsx-1652325292362)(%E5%89%91%E6%8C%87Offer.assets/1652014725810.png)]

思路分析:

丑数就是指是由2,3,5相互乘组成的数。所以我们就有了第一个想法,就是用这个规律来写一个。同时我们还可以进行改进,就是只要可以除出来的数是我们之前判断过的丑数,就说明这个数也是丑数。

public class Solution40 {
    public static int nthUglyNumber(int n) {
        HashSet<Integer> set = new HashSet<>();
        set.add(1);
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            int number = isnthUglyNumber(dp[i - 1] + 1, set);
            set.add(number);
            dp[i] = number;
        }
        return dp[n - 1];
    }

    /**
     *
     * @param num dp[i - 1] + 1
     * @return
     */
    public static int isnthUglyNumber(int num, HashSet set) {
        boolean flag = true;
        while (flag) {
            if ((set.contains(num / 2) && num % 2 == 0) || (set.contains(num / 3) && num % 3 == 0) || (set.contains(num / 5) && num % 5 == 0)) {
                flag = false;
            }else {
                num++;
            }
        }
        return num;
    }
}

结果就是超时了。。。

所以得换种方法去写,也就是写一种真正的动态规划来完成这一步。首先去想表关系,第一个位置是1,之后的位置为2,3,4,5,6,我们发现2是由1 * 2得到的,3是1 * 3得到的,4是2 * 2得到的,5是1 * 5得到的。

所以我们得到一个结论就是每一个数字好像都是从 * 5,* 2, * 3一步步往后走的,但是我们选取的是最小值从而保证单调性。

class Solution {
    public int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
            int number = Math.min(n2, Math.min(n3, n5));
            dp[i] = number;
            if (number == n2) a++;
            if (number == n3) b++;
            if (number == n5) c++;
        }
        return dp[n - 1];
    }
}

47, 第一个只出现一次的字符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kqf8DoKv-1652325292362)(%E5%89%91%E6%8C%87Offer.assets/1652016191392.png)]

思路分析:

这个题和之前的不含重复的子串是类似的,我们仍然需要一个存储数组用于判断出了几次,而且他要求要找的是第一个,所以得用LinkedHashMap。之后遍历一下LinkedHashMap,找到第一次出现一次的字符。

class Solution {
    public char firstUniqChar(String s) {
        HashMap<Character, Integer> hashMap = new LinkedHashMap<>(26);
        char res = ' ';
        if (s.length() == 0 || s == "") {
            return ' ';
        }
        for (int i = 0; i < s.length(); i++) {
            char charAt = s.charAt(i);
            if (!hashMap.containsKey(charAt)) {
                hashMap.put(charAt, 1);
            }else {
                hashMap.put(charAt, 2);
            }
        }
        for(Map.Entry<Character, Integer> d : hashMap.entrySet()){
            if (d.getValue() == 1) return d.getKey();
        }
        return res;
    }
}

48,两个链表的第一个公共节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5594uQn-1652325292362)(%E5%89%91%E6%8C%87Offer.assets/1652176565383.png)]

思路分析:

因为是我们要寻求的是公告的交点,就意味着后面会有至少一个公共子串。那么我们只要判断谁长,长的减去短的,就可以找到公共交点前一个点。从而去找公共交点。

代码实现:

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution42
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/5/10 17:19
 */
public class Solution42 {
    public static class ListNode {
     int val;
     ListNode next;
     ListNode(int x) {
         val = x;
         next = null;
     }
    }
    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int countA = 1;
        int countB = 1;
        ListNode A = headA;
        ListNode B = headB;
        if (headA == null || headB == null) {
            return null;
        }
        while (A.next != null) {
            countA++;
            A = A.next;
        }
        while (B.next != null) {
            countB++;
            B = B.next;
        }
        A = headA;
        B = headB;
        if (countA > countB) {
            int m = countA -countB;
            while (m != 0) {
                A = A.next;
                m--;
            }
        }
        if (countB > countA) {
            int m = countB -countA;
            while (m != 0) {
                B = B.next;
                m--;
            }
        }
        while (A != B) {
            if (A.next == null || B.next == null) {
                return null;
            }
            A = A.next;
            B = B.next;

        }
        return A;
    }

    public static void main(String[] args) {
        ListNode a = new ListNode(1);
        ListNode b = new ListNode(2);
        ListNode c = new ListNode(3);
        ListNode d = new ListNode(4);
        ListNode e = new ListNode(5);
        ListNode f = new ListNode(6);
        a.next = b;
        b.next = c;
        d.next = b;
        ListNode intersectionNode = getIntersectionNode(a, d);
        System.out.println(intersectionNode.val);
    }
}

49,在排序数组中查找数字 I

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dpj4KgMy-1652325292363)(%E5%89%91%E6%8C%87Offer.assets/1652181247284.png)]

思路分析:

很明显二分法,找到这个值之后,可以从当前值往左右两边扩散,也可以从左右边界往里走。但是我自己刚开始写的是第一种,超时了,可能的原因是需要判断是否越界导致的吧。

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution44
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/5/10 17:59
 */
public class Solution44 {
    public static int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        int count = 0;
        while(left <= right) {
            mid = left + ((right - left) >> 1);
            if (nums[mid] > target) {
                right = mid - 1;
            }else if (nums[mid] < target) {
                left = mid + 1;
            }else {
                if(nums[right]!=target)
                    right--;
                else if(nums[left]!=target)
                    left++;
                else
                    break;
            }
        }
        return right - left + 1;

    }

    public static void main(String[] args) {
        int[] arr = {5, 7,7,8,8,10};
        System.out.println(search(arr, 8));
    }
}

50,0~n-1中缺失的数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mo6WtGGM-1652325292363)(%E5%89%91%E6%8C%87Offer.assets/1652325086744.png)]

思路分析:

这道题的核心思路在于看我们的下标和数组中的数值是否可以一一对应。所以就有了两种方法,一种是二分,一种是循环。

代码实现:

public class Solution45 {
    public int missingNumber(int[] nums) {
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i != nums[i]) {
                return i;
            }
        }
        res = nums.length;
        return res;
    }

    public int missingNumber2(int[] nums) {
        int l = 0, r = nums.length - 1;
        while (l <= r) {
            int mid = l + ((r - l) >> 1);
            if (mid == nums[mid]) {
                l = mid + 1;
            }else {
                r = mid -1;
            }
        }
        return l;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值