《剑指Offer》笔记5.2

时间效率

  1. 数组中出现次数超过一半的数字
  2. 最小的 k 个数
  3. 数据流中的中位数
  4. 连续子数组的最大值
  5. 1~n 整数中 1 出现的次数
  6. 数字序列中某一位的数字
  7. 把数组排成最小的数
  8. 把数组翻译成字符串
  9. 礼物的最大价值
  10. 最长不含重复字符的子字符串

39:数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如,输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5此,超过数组长度的一半,因此输出2。

思路:数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此,在遍历数组时保存两个值:一个是数组中的一个数字;另一个是次数。当遍历到下一个数字的时候,如果下一个数字和保存的数字相同,则次数加1;如果不同,则次数减1。如果次数为0,则保存下一个数字,并把次数设为1。由于要找的数字出现的次数是最多的,那么要找的数字肯定是最后保存的数字。

class Solution {
    public int majorityElement(int[] nums) {
        if(nums.length==0) return null;
        
        int num = nums[0];
        int count = 1;
        for(int i=1;i<nums.length;i++){
            if(num == nums[i]) count++;
            else{
                count--;
                if(count==0){
                    num = nums[i];
                    count = 1;
                }
            }
        }
        return num;
    }
}

40:最小的 k 个数

题目:输入 n 个整数,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8 这8个数字,则最小的4个数字是1、2、3、4。

思路

  1. 排序之后位于最前面的 k 个数就是最小的 k 个数。时间复杂度O(nlogn)
  2. 基于 Partition 函数来实现,如果基于数组的第 k 个数字来调整,使得比第 k 个数字小的所有数字都位于数组的左边,比第 k 个数字大的都在右边。这样调整后,位于数组中左边的 k 个数字就是最小的 k 个数字(这 k 个数字不一定是排序的)
  3. 最大堆,选取前K个数建立最大堆,每次从原数组中取一个元素与根进行比较,如果大于根结点的元素,忽视,取下一个数组元素继续该过程;如果小于根结点的元素,则将其加入最大堆,并进行堆调整,将根元素移动到最后再删除,即保证最大堆中的元素仍然是排名前K的数,且根元素仍然最大。时间复杂度O(nlogk)
// 思路1
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(k<=0 || arr.length<k) return new int[0];
        quickSort(arr, 0, arr.length-1);
        return Arrays.copyOf(arr, k);
    }
    private void quickSort(int[] arr, int start, int end){
        if(start < end){
            int pivot = arr[start];
            int i = start, j = end;
            while(i<j){
                while(i<j && arr[j]>pivot) j--;
                if(i<j) arr[i++] = arr[j];
                while(i<j && arr[i]<pivot) i++;
                if(i<j) arr[j--] = arr[i];
            }
            arr[i] = pivot;
            quickSort(arr, start, i-1);
            quickSort(arr, i+1, end);
        }
    }
}

41:数据流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

思路

  1. 将数据流保存在一个列表中,并在添加元素时 保持数组有序 。此方法的时间复杂度为 O(N),其中包括: 查找元素插入位置 O(log N)(二分查找)、向数组某位置插入元素 O(N)(插入位置之后的元素都需要向后移动一位)
  2. 建立一个 小顶堆 和 大顶堆 ,各保存列表的一半元素,且规定:
    a. 小顶堆保存较大的一半,长度为 N/2(N为偶数)或 (N+1)/2(N为奇数);
    b. 大顶堆保存较小的一半,长度为 N/2(N为偶数)或 (N+1)/2(N为奇数);
class MedianFinder {
    Queue<Integer> minHeap, maxHeap;
    /** initialize your data structure here. */
    public MedianFinder() {
        minHeap = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        maxHeap = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }
    
    public void addNum(int num) {
        if(minHeap.size() != maxHeap.size()) {
            minHeap.add(num);
            maxHeap.add(minHeap.poll());
        } else {
            maxHeap.add(num);
            minHeap.add(maxHeap.poll());
        }
    }
    
    public double findMedian() {
        return minHeap.size() != maxHeap.size() ? minHeap.peek() : (minHeap.peek() + maxHeap.peek()) / 2.0;
    }
}
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

42:连续子数组的最大值

题目:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。

思路:动态规划,设动态规划列表 dp,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。转移方程: 若 dp[i−1]≤0,说明 dp[i-1] 对 dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大

class Solution {
    public int maxSubArray(int[] nums) {
        if(nums.length == 0) return 0;
        int res = nums[0];
        for(int i=1;i<nums.length;i++){
            nums[i] += Math.max(nums[i-1], 0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }
}

43:1~n 整数中 1 出现的次数

题目:输入一个整数 n,求 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如,输入12,1~12 这些整数中包含 1 的整数有 1、10、11、12,1 一共出现 5 次。

思路:将 1 ~ n 的个位、十位、百位、…的 11 出现次数相加,即为 1 出现的总次数。设数字 n 是个 x 位数,记 n 的第 i 位为 ni,则可将 n 写为 nxnx-1……n2n1
• 称 ni 为当前位,记为 cur;
• 称 ni-1ni-2…n2n1 为低位,记为low;
• 称 nxnx-1…nini+1 为高位,记为high;
• 称10i 为为因子,记为 digit。
• 当 cur = 0 时,此位 1 的出现次数只由高位 high 决定,计算公式为:high * digit;
• 当 cur = 1 时,此位 1 的出现次数由高位 high 和低位 low 决定,计算公式为:high * digit + low + 1;
• 当 cur > 1 时,此位 1 的出现次数只由高位 high 决定,计算公式为:high * digit + digit;

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

44:数字序列中某一位的数字

题目:数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第 n 位对应的数字。

思路
• 确定 n 所在数字的位数 ,记为 digit;
• 确定 n 所在的数字 ,记为 num;
• 确定 n 是 num 中的哪一数位,并返回结果。

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.
    }
}

45:把数组排成最小的数

题目:输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如,输入数组{3,32,321},则打印出这3个数字能排成的最小数字 321323。

思路:拼接起来的“最小数字”,本质上是一个排序问题。排序判断规则: 设 nums 任意两数字的字符串格式 x 和 y ,若拼接字符串 x + y > y + x,则 x > y;反之,x < y ;

class Solution {
    public String minNumber(int[] nums) {
        String[] strs = new String[nums.length];
        for(int i=0;i<nums.length;i++){
            strs[i] = String.valueOf(nums[i]);
        }
        quickSort(strs, 0, strs.length-1);
        // Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));
        StringBuilder res = new StringBuilder();
        for(String s : strs){
            res.append(s);
        }
        return res.toString();
    }
    private void quickSort(String[] strs, int l, int r){
        if(l < r){
            String pivot = strs[l];
            int i = l, j = r;
            while(i<j){
                while(i<j && compare(strs[j], pivot) > 0) j--;
                if(i<j) strs[i++] = strs[j];
                while(i<j && compare(strs[i], pivot) < 0) i++;
                if(i<j) strs[j--] = strs[i];
            }
            strs[i] = pivot;
            quickSort(strs, l, i-1);
            quickSort(strs, i+1, r);
        }
    }
    private int compare(String a, String b){
        return (a + b).compareTo(b + a);
    }
}

46:把数字翻译成字符串

题目:给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。例如,12258 有 5 种不同的翻译,分别是“bccfi”、“bwfi”、“bczi”、“mcfi”和“mzi”。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

思路:动态规划,设动态规划列表 dp,dp[i] 代表以第 i 位为结尾的数字的翻译方案数量,若第 i-1 位和第 i 位组成的数组可以被翻译,则dp[i] = dp[i-1]+dp[i-2],否则,dp[i] = dp[i-1]。

class Solution {
    public int translateNum(int num) {
        String str = String.valueOf(num);
        int dp0 = 1, dp1 =1;
        for(int i=2;i<=str.length();i++){
            String s = str.substring(i - 2, i);
            int temp = s.compareTo("10")>=0 && s.compareTo("25")<=0 ? dp0 + dp1:dp1;
            dp0 = dp1;
            dp1 = temp;
        }
        return dp1;
    }
}

47:礼物的最大价值

题目:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

思路:动态规划,dp[i][j] 为从(0, 0) 到(i, j),能获得的最大价值,dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]

class Solution {
    public int maxValue(int[][] grid) {
        if(grid.length==0 && grid[0].length==0) return 0;
        int m = grid.length, n = grid[0].length;
        int[] dp = new int[n];
        dp[0] = grid[0][0];
        for(int i=1;i<n;i++) dp[i] = dp[i-1] + grid[0][i];
    
        for(int i=1;i<m;i++){
            dp[0] += grid[i][0];
            for(int j=1;j<n;j++){
                dp[j] = (dp[j-1]>dp[j]?dp[j-1]:dp[j]) + grid[i][j];
            }
        }
        return dp[n-1];
    }
}

48:最长不含重复字符的子字符串

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含‘a’~‘z’的字符。例如,在字符串“arabcacfr”中,最长的不含重复字符的子字符串是“acfr”,长度为4

思路:动态规划 + 哈希表,使用哈希表记录各字符最后一次出现的索引位置 。子字符串的长度 = 字符这次位置 - 字符上次位置。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int curLen = 0;
        int maxLen = 0;
        Map<Character, Integer> dic = new HashMap<>();
        for(int i=0;i<s.length();i++){
            int pos = dic.getOrDefault(s.charAt(i), -1);
            dic.put(s.charAt(i), i);
            curLen = curLen < i - pos ? curLen + 1 : i - pos;
            maxLen = Math.max(maxLen, curLen);
        }
        return maxLen;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值