力扣记录:剑指offer(5)——JZ43-52

JZ43 1~n整数中1出现的次数

  • 按位递推计算,如果当前位的值为0,当前位1的出现次数只由高位决定(高位 * 当前位数);如果当前位的值为1,当前位1的出现次数由高位和低位决定(高位 * 当前位数+低位+1);如果当前位为2~9,当前位1的出现次数由高位决定((高位+1) * 当前位数)
    • 时间复杂度O(logn),空间复杂度O(1)
class Solution {
    public int countDigitOne(int n) {
        //按位递推计算
        //初始化
        int cur = n % 10;
        int low = 0;
        int high = n / 10;
        int digit = 1;
        int res = 0;
        //递推
        while(high != 0 || cur != 0){
            if(cur == 0){
                res += high * digit;
            }else if(cur == 1){
                res += high * digit + low + 1;
            }else{
                res += (high + 1) * digit;
            }
            //下一轮,注意顺序
            low += cur * digit;
            cur = high % 10;
            high /= 10;
            digit *= 10;
        }
        return res;
    }
}

JZ44 数字序列中某一位的数字

  • 先判断在哪个占位范围,再判断在范围内第几个数的第几位(可转为字符串计算),注意部分数值需要使用long定义防止溢出
    • 时间复杂度O(logn),空间复杂度O(logn)
class Solution {
    public int findNthDigit(int n) {
        //判断第一位为0
        if(n == 0) return 0;
        //按数字占位分类:1-9,10-99,100-999,...start(10的i-1次方)-10的i次方减1
        //n位数占位为别为:1,2,3,...i++
        //n位数个数分别为:9,90,900,...num = 9*start
        //n位数占位总数分别为:9,180,2700,...sum = 9*start*i
        //先判断n在在哪个占位范围
        int i = 1;
        long start = 1;
        long num = 9;
        long sum = 9;
        while(n >= sum){
            n -= sum;
            i++;
            start *= 10;
            num = 9 * start;
            sum = num * i;
        }
        //此时得到n在占位i范围内,接下来判断n在第几个数的位置
        int a = n / i;  //第a个数
        int b = n % i;  //第b位
        //余数为0时返回第a-1个数的最后一位
        if(b == 0) return getNum(start + a - 1, b);
        //否则返回第a个数的第b位
        return getNum(start + a, b);
    }
    //求数字在某位的数,输入数字,余数,数字占位
    private int getNum(long num, int digit){
        //转为字符串处理
        String num_str = String.valueOf(num);
        if(digit == 0) return num_str.charAt(num_str.length() - 1) - '0'; //余数为0时返回个位
        //余数大于0从第一位开始计算
        return num_str.charAt(digit - 1) - '0';
    }
}

JZ45 把数组排成最小的数

  • 使用快排,将遍历数组,排序规则为将两数拼接后的较小值作为顺序(使用字符串compareTo()逐个比较ASCII码大小)
    • 时间复杂度O(nlogn),空间复杂度O(n)
class Solution {
    public String minNumber(int[] nums) {
        int leng = nums.length;
        //将数组转为字符串数组按照规则进行排序
        String[] numsStr = new String[leng];
        for(int i = 0; i < leng; i++){
            numsStr[i] = String.valueOf(nums[i]);
        }
        //快速排序
        quickSort(numsStr, 0, leng - 1);
        //拼接排好序的字符串
        StringBuilder sb = new StringBuilder("");
        for(String s : numsStr){
            sb.append(s);
        }
        return sb.toString();
    }
    //排序函数,快速排序,将两数拼接后的较小值作为拼接顺序,输入字符串数组,左右区间(左闭右闭)
    private void quickSort(String[] numsStr, int left, int right){
        if(right - left < 1) return;
        //选取左边界为哨兵,左小右大
        int i = left;
        int j = right;
        while(i < j){
            while(i < j && (numsStr[left] + numsStr[j]).compareTo(numsStr[j] + numsStr[left]) <= 0) j--;
            while(i < j && (numsStr[left] + numsStr[i]).compareTo(numsStr[i] + numsStr[left]) >= 0) i++;
            swap(numsStr, i, j);
        }
        swap(numsStr, i, left); //交换哨兵到中间(和右侧最后一个小于的数交换)
        //递归排序
        quickSort(numsStr, left, i - 1);
        quickSort(numsStr, i + 1, right);
    }
    //交换函数
    private void swap(String[] numsStr, int i, int j){
        String temp = numsStr[i];
        numsStr[i] = numsStr[j];
        numsStr[j] = temp;
    }
}

JZ46 把数字翻译成字符串

  • 动态规划,一个或两个数字组成一个字母(青蛙爬楼梯),判断两个数字是否符合条件
    • 时间复杂度O(logn),空间复杂度O(logn)
class Solution {
    public int translateNum(int num) {
        if(num < 10) return 1;
        //动态规划DP
        //定义dp数组表示num在下标0到i(包括i)的表达方法有dp[i]种
        String numStr = String.valueOf(num);
        //int[] dp = new int[numStr.length()];
        //初始化
        // dp[0] = 1;
        // if(numStr.substring(0, 2).compareTo("25") <= 0){
        //     dp[1] = 2;
        // }else{
        //     dp[1] = 1;
        // }
        int a = 1;
        int b = 1;
        if(numStr.substring(0, 2).compareTo("25") <= 0){
            b = 2;
        }
        int c = b;
        //正序遍历
        for(int i = 2; i < numStr.length(); i++){
            if(numStr.substring(i - 1, i + 1).compareTo("25") <= 0 && numStr.substring(i - 1, i + 1).compareTo("10") >= 0){
                //dp[i] = dp[i - 1] + dp[i - 2];
                c = a + b;
            }else{
                //dp[i] = dp[i - 1];
                c = b;
            }
            a = b;
            b = c;
        }
        //return dp[numStr.length() - 1];
        return c;
    }
}

JZ47 礼物的最大价值

  • 动态规划,当前位置得到的最大价值为左边和右边对比(不同路径),使用滚动数组
    • 时间复杂度O(m*n),空间复杂度O(n)(在原地修改只需O(1))
class Solution {
    public int maxValue(int[][] grid) {
        //动态规划
        int m = grid.length;
        int n = grid[0].length;
        //定义dp数组dp[i][j]表示从左上角到点(i,j)所获得礼物的最大价值
        // int[][] dp = new int[m][n];
        int[] dp = new int[n];
        //初始化
        // dp[0][0] = grid[0][0];
        // for(int i = 1; i < m; i++){
        //     dp[i][0] = dp[i - 1][0] + grid[i][0];
        // }
        // for(int j = 1; j < n; j++){
        //     dp[0][j] = dp[0][j - 1] + grid[0][j];
        // }
        dp[0] = grid[0][0];
        for(int j = 1; j < n; j++){
            dp[j] = dp[j - 1] + grid[0][j];
        }
        //正向遍历,从上到下从左到右
        for(int i = 1; i < m; i++){
            dp[0] += grid[i][0];
            for(int j = 1; j < n; j++){
                dp[j] = Math.max(dp[j - 1], dp[j]) + grid[i][j];
            }
        }
        // return dp[m - 1][n - 1];
        return dp[n - 1];
    }
}

JZ48 最长不含重复字符的子字符串

  • 动态规划,子串问题(连续)
    • 时间复杂度O(n^2),空间复杂度O(1)
class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() == 0) return 0;
        //动态规划
        //定义dp数组dp[i]表示下标x(0<=x<=i)到i(包括i)之前的子串最长不重复子串长度为dp[i]
        // int[] dp = new int[s.length()];
        int dp = 1;
        //初始化
        // dp[0] = 1;
        int max = 1;
        for(int i = 1; i < s.length(); i++){
            int j = i - 1;
            while(j >= 0){
                if(s.charAt(i) == s.charAt(j)){
                    break;
                }else{
                    j--;
                }
            }
            // if(dp[i - 1] >= i - j){
            //     dp[i] = i - j;
            // }else{
            //     dp[i] = dp[i - 1] + 1;
            // }
            // max = Math.max(dp[i], max);
            if(dp >= i - j){
                dp = i - j;
            }else{
                dp += 1;
            }
            max = Math.max(dp, max);
        }
        return max;
    }
}
  • 双指针(滑动窗口)+哈希表,子串问题(连续),右指针向前遍历字符串,哈希表统计右指针指向字符最后一次出现的位置;当哈希表更新时(出现重复字符时),左指针移动到哈希表更新前的位置,得到此时右指针指向的下标之前的最长子串(右指针-左指针),同时更新最大值;右指针继续向前移动
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() == 0) return 0;
        //双指针(滑动窗口)+哈希表
        int left = -1;
        int right = 0;
        int max = 1;
        Map<Character, Integer> map = new HashMap<>();
        //右指针向前遍历字符串,哈希表统计右指针指向字符最后一次出现的位置
        while(right < s.length()){
            //当哈希表更新时(出现重复字符时),左指针移动到哈希表更新前的位置
            if(map.containsKey(s.charAt(right))){
                //注意,左指针只能向前移动
                left = Math.max(left, map.get(s.charAt(right)));
            }
            //得到此时右指针指向的下标之前的最长子串(右指针-左指针),同时更新最大值
            map.put(s.charAt(right), right);
            max = Math.max(max, right - left);
            //右指针继续向前移动
            right++;
        }
        return max;
    }
}

JZ49 丑数

  • 动态规划,每个丑数都是由较小的丑数乘2,3,5得到,使用三指针,指针指向的丑数分别乘2,3,5,取其中最小值对应指针向前移动。
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public int nthUglyNumber(int n) {
        //动态规划
        //定义dp数组表示第i个丑数为dp[i]
        int[] dp = new int[n + 1];
        dp[1] = 1;
        //定义三指针分别乘2,3,5
        int a = 1;
        int b = 1;
        int c = 1;
        for(int i = 2; i <= n; i++){
            //取其中最小值对应指针向前移动
            dp[i] = Math.min(Math.min(dp[a] * 2, dp[b] * 3), dp[c] * 5);
            if(dp[i] == dp[a] * 2) a++;
            if(dp[i] == dp[b] * 3) b++;
            if(dp[i] == dp[c] * 5) c++;
        }
        return dp[n];
    }
}

JZ50 第一个只出现一次的字符

  • 两次遍历,第一次使用哈希表存储字符及其频率(可使用布尔值表示频率是否符合条件,超过1则为false),第二次返回第一个频率为1的字符。可使用有序哈希表(LinkedHashMap)则第二次不需要遍历字符串只需要遍历有序哈希表。
    • 时间复杂度O(n),空间复杂度O(|字符集大小(最大为26)|)
class Solution {
    public char firstUniqChar(String s) {
        Map<Character, Integer> map = new HashMap<>();
        char[] sArr = s.toCharArray();
        for(char c : sArr){
            if(!map.containsKey(c)) map.put(c, 0);
            map.put(c, map.get(c) + 1);
        }
        for(char c : sArr){
            if(map.get(c) == 1) return c;
        }
        return ' ';
    }
}

JZ51 数组中的逆序对*

  • 归并排序——>逆序对,分而治之,归并算法划分后进行合并时对左右数组进行判断,定义左右指针 i ,j 分别指向左右数组起始位置。当 j 向前移动时(右边值较小),逆序对数量为(左边数组长度 - i);直到左右数组遍历完毕,进入下一轮合并判断
    • 时间复杂度O(nlogn),空间复杂度O(n)
class Solution {
    int count;
    public int reversePairs(int[] nums) {
        if(nums.length <= 1) return 0;
        int[] nums2 = new int[nums.length];
        int[] temp = new int[nums.length];
        count = 0;
        for(int i = 0; i < nums.length; i++){
            nums2[i] = nums[i];
        }
        //归并排序
        mergeSort(nums2, 0, nums.length - 1, temp);
        return count;
    }
    //归并排序,输入待排序数组及其左右区间(左闭右闭)和一个临时数组
    private void mergeSort(int[] nums, int left, int right, int[] temp){
        //终止条件
        if(right - left < 1) return;
        //递归划分
        int mid = (left + right) / 2;
        //分别对左右两部分归并排序
        mergeSort(nums, left, mid, temp);
        mergeSort(nums, mid + 1, right, temp);
        //合并判断
        merge(nums, left, mid, right, temp);
    }
    //合并判断,输入待排序数组及其左右两部分区间和一个临时数组,过程中计算逆序对数量
    private void merge(int[] nums, int left, int mid, int right, int[] temp){
        //左右指针分别指向两部分开头位置
        int i = left;
        int j = mid + 1;
        int index = left;  //将排好序的数按序放入临时数组
        //遍历左右部分,将较小值放入临时数组
        while(i <= mid && j <= right){
            if(nums[i] <= nums[j]){
                temp[index++] = nums[i++];
            }else{  //当右指针移动时(右边值较小)计算逆序对数量为(左边数组长度 - i)
                temp[index++] = nums[j++];
                count += mid - i + 1;
            }
        }
        //逆序对计算完了,数组排序还未完成
        //左边先遍历完
        while(j <= right) temp[index++] = nums[j++];
        //右边先遍历完
        while(i <= mid) temp[index++] = nums[i++];
        //将排序好的临时数组复制到原数组
        index = left;
        while(left <= right){
            nums[left++] = temp[index++];
        }
    }
}

JZ52 两个链表的第一个公共节点

  • 两个链表相交,则将两个链表按尾部对齐,其相交节点在同一位置,因此需要先分别遍历两个链表得到其长度,再次遍历时比较相应位置的指针即可
    • 时间复杂度O(m+n),空间复杂度O(1)
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = 1;
        int lenB = 1;
        ListNode curA = headA;
        ListNode curB = headB;
        //分别求两个链表的长度
        while(curA != null){
            lenA++;
            curA = curA.next;
        }
        while(curB != null){
            lenB++;
            curB = curB.next;
        }
        //将A链表始终定义为长的那个
        if(lenA < lenB){
            curA = headB;
            curB = headA;
            int temp = lenA;
            lenA = lenB;
            lenB = temp;
        }else{
            curA = headA;
            curB = headB;
        }
        //对应位置比较
        while(lenA > lenB){
            curA = curA.next;
            lenA--;
        }
        while(curA != null){
            if(curA == curB) return curA;
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}
  • 两个链表相交,两个指针分别从两个链表头出发,逐个比较并向后移动,若两个指针不同时为null,则将null指针指向另一个链表头继续向后移动,直到两个指针同时为null
    • 时间复杂度O(m+n),空间复杂度O(1)
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode curA = headA;
        ListNode curB = headB;
        while(curA != null || curB != null){
            if(curA == null) curA = headB;
            if(curB == null) curB = headA;
            if(curA == curB){
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值