剑指offer刷题笔记--Num41-50

目录

1--数据流中的中位数(41)

2--连续子数组的最大和(42)

3--1~n中整数中 1 出现的次数(43)

4--数字序列中某一位的数字(44)

5--把数组排成最小的数(45)

6--把数字翻译成字符串(46)

7--礼物的最大价值(47)

8--最长不含重复字符的子字符串(48)

9--丑数(49)

10--第一个只出现一次的字符(50)


1--数据流中的中位数(41)

 主要思路:

      维护两个优先队列,Q1大数优先,存储比中位数小的数;Q2小数优先,存储比中位数大的数;

       当存储的数为偶数时,Q1.size() == Q2.size(), 中位数为:(Q1.top() + Q2.top()) / 2.0

       当存储的数为奇数时,Q2.size() = Q1.size() + 1, 中位数为 Q2 的队头元素

       因此插入元素,要确保Q2.size() >= Q1.size();

       同时插入新元素时,要先插入到另一个队列中,确保有序,取队头元素再插入到目的元素中;

#include <iostream>
#include <queue>   
#include <algorithm>

class MedianFinder {
public:
    MedianFinder() {}
    
    void addNum(int num) {
        // Q1大数优先,存储比中位数小的数
        // Q2小数优先,存储比中位数大的数
        // 当存储的数为偶数时,Q1.size() == Q2.size(), 中位数为:(Q1.top() + Q2.top()) / 2.0
        // 当存储的数为奇数时,Q2.size() = Q1.size() + 1, 中位数为 Q2 的队头元素
        // 因此插入元素,要确保Q2.size() >= Q1.size(); 
        // 同时插入新元素时,要先插入到另一个队列中,确保有序,取队头元素再插入到目的元素中
        if(Q1.size() == Q2.size()){
            Q1.push(num);
            int top = Q1.top();
            Q1.pop();
            Q2.push(top);
        }
        else{
            Q2.push(num);
            int top = Q2.top();
            Q2.pop();
            Q1.push(top);
        }
    }
    
    double findMedian() {
        if(Q1.size() == Q2.size()){
            return (Q1.top() + Q2.top()) / 2.0;
        }
        else{
            return Q2.top()*1.0;
        }
        
    }
private:
    std::priority_queue<int, std::vector<int>, std::less<int>> Q1; // 大数优先级高,队头
    std::priority_queue<int, std::vector<int>, std::greater<int>> Q2; // 小数优先级高,队头
};


int main(int argc, char *argv[]){
    MedianFinder S1;
    S1.addNum(1);
    S1.addNum(4);
    S1.addNum(2);
    S1.addNum(3);
    double Res = S1.findMedian();
    std::cout << Res << std::endl;
    return 0;
}

2--连续子数组的最大和(42)

主要思路:

        使用动态规划解决: 

#include <iostream>
#include <vector>

class Solution {
public:
    int maxSubArray(std::vector<int>& nums) {
        int len = nums.size();
        if(len == 0) return 0;

        std::vector<int> dp;
        dp.push_back(nums[0]);
        int max = nums[0];
        for(int i = 1; i < len; i++){
            if(dp[i - 1] >= 0){
                dp.push_back(dp[i-1] + nums[i]);
            }
            else{
                dp.push_back(nums[i]);
            }
            if (dp[i] > max) max = dp[i];
        }
        return max;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    std::vector<int> test = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int Res = S1.maxSubArray(test);
    std::cout << Res << std::endl;
    return 0;
}

3--1~n中整数中 1 出现的次数(43)

主要思路:视频讲解题解

        规律题,当固定住其中 1 位时,计算其出现1的次数,分别统计个位、百位、千位等数字出现1的个数并求和相加即可;

#include <iostream>
#include <vector>

class Solution {
public:
    int countDigitOne(int n) {
        long long bit = 1;
        long long sum = 0;
        while(bit <= n){
            long long cur = n / bit % 10;
            long long low = n % bit;
            long long high = n / bit / 10;
            if(cur == 0){
                sum += high * bit;
            }
            else if(cur == 1){
                sum += high*bit + low + 1;
            }
            else{
                sum += (high+1)*bit;
            }
            bit = bit*10;
        }
        return (int)sum;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int test = 12;
    int sum = S1.countDigitOne(test);
    std::cout << sum << std::endl;
    return 0;
}

4--数字序列中某一位的数字(44)

主要思路:视频讲解

        规律题,先判断 n 属于哪个范围的数(个位、百位、千位等),再计算 n 具体属于哪一个数,最后计算 n 属于某一个数的具体哪一位,返回即可;

#include <iostream>
#include <vector>
#include <cmath>

class Solution {
public:
    int findNthDigit(int n) {
        if(n == 0) return 0;
        long bit = 1; // 1 10 100
        int i = 1; // 1 2 3
        long count = 9; // 9 180 2700 // 范围内字符的个数 
        // 确定属于哪一个范围
        while(count < n){
            n = n - count;
            i = i + 1;
            bit = bit * 10;
            count = bit * i * 9;
        }
        // 确定属于哪一个数
        long num = bit + (n-1) / i;
        // 确定具体哪一位
        int index = (n - 1) % i + 1;
        int res = (int)(num / pow(10, i - index)) % 10;
        return res;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int test = 19;
    int res = S1.findNthDigit(test);
    std::cout << res << std::endl;
    return 0;
}

5--把数组排成最小的数(45)

主要思路:

        拼接字符串 x+y > y+x, 则 x>y;

        拼接字符串 x+y < y+x, 则 x<y;

         使用快排对字符串数组进行排序,要确保由字符串数组组成的数最小,即strs[0]strs[1]strs[2]...最小,必须连续满足strs[0] < strs[1], strs[1] < strs[2];

#include <iostream>
#include <vector>
#include <string>

class Solution {
public:
    std::string minNumber(std::vector<int>& nums) {
        std::string Res;
        if (nums.size() == 0) return Res; 

        std::vector<std::string> strs;
        // 数字 -> string
        for(int item : nums){
            strs.push_back(std::to_string(item));
        }

        quickSort(strs, 0, nums.size()-1);
        for(std::string str : strs){
            Res.append(str);
        }
        return Res;
    }
    
    void quickSort(std::vector<std::string>& strs, int left, int right){
        if (left >= right) return;
        std::string pivot = strs[left];
        int i = left, j = right;
        while(i < j){
            while(i < j && strs[j] + pivot >= pivot + strs[j]) j--; // 拼接字符串 x+y > y+x, 则 x>y
            strs[i] = strs[j];
            while(i < j && strs[i] + pivot <= pivot + strs[i]) i++; // 拼接字符串 x+y < y+x, 则 x<y
            strs[j] = strs[i]; 
        }
        strs[i] = pivot;
        quickSort(strs, left, i-1);
        quickSort(strs, i+1, right);
    }
};
 
int main(int argc, char *argv[]){
    Solution S1;
    std::vector<int> test = {3, 30, 34, 5, 9};
    std::string Res = S1.minNumber(test);
    std::cout << Res << std::endl;
    return 0;
}

6--把数字翻译成字符串(46)

主要思路:

        基于动态规划定义状态和转移方程:dp[i] 代表以 x_i​ 为结尾的数字的翻译方案数量;

#include <iostream>
 
class Solution {
public:
    int translateNum(int num) {
        if(num < 10) return 1;
        int dp_1 = 1;
        int dp_2 = 1;
        int dp;
        while(num != 0){
            int tmp = ((num/10) % 10) * 10 + (num % 10);
            if(9 < tmp && tmp < 26){
                dp = dp_1 + dp_2;
            }
            else{
                dp = dp_1;
            }
            dp_2 = dp_1;
            dp_1 = dp;
            num = num / 10;
        }
        return dp;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int num = 12258;
    int Res = S1.translateNum(num);
    std::cout << Res << std::endl;

    return 0;
}

7--礼物的最大价值(47)

用递归深搜会超时,改用动态规划:

       状态:dp[i][j] 表示走到当前位置(i, j)的累计礼物最大值;

       转移方程:dp[i][j] = grid[i][j] + max(dp[i-1][j], dp[i][j-1]);

#include <iostream>
#include <vector>

class Solution {
public:
    int maxValue(std::vector<std::vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                // 原地修改,不额外创建dp数组
                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] += std::max(grid[i-1][j], grid[i][j-1]); 
                }
            }
        }
        return grid[m-1][n-1];
    }
};

int main(int argc, char *argv[]){
    std::vector<std::vector<int>> test = {{1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
    
    Solution S1;
    int Res = S1.maxValue(test);
    std::cout << Res << std::endl;

    return 0;
}

8--最长不含重复字符的子字符串(48)

主要思路:

        基于双指针滑动窗口,当滑动窗口不出现重复元素时,右指针右移不断纳入新的元素;当滑动窗口出现重复元素时,左指针右移来移除元素;记录滑动窗口的最大长度即可;

        判断滑动窗口是否出现重复元素可以采用哈希表,key 为字符,当出现重复字符时,只需判断字符是否已出现在哈希表中;

#include <iostream>
#include <string>
#include <map>

class Solution {
public:
    int lengthOfLongestSubstring(std::string s) {
        int len = s.length();
        int j = 0;
        int max_len = 0;
        std::map<char, int> M;
        for(int i = 0; i < len; i++){     
            while(j < len && !M.count(s[j])){
                M.insert(std::pair<char, int>(s[j], 1)); // 右指针右移,把右指针指向的值纳入滑动窗口
                j++;
            }
            max_len = std::max(max_len, j - i);
            M.erase(s[i]); // 左指针右移前,需要把当前左指针指向的值移出滑动窗口
        }
        return max_len;
    }
};

int main(int argc, char *argv[]){
    std::string test = "abcabcbb";
    Solution S1;
    int res = S1.lengthOfLongestSubstring(test);
    std::cout << res << std::endl;
    return 0;
}

9--丑数(49)

主要思路:

#include <iostream>
#include <vector>

class Solution {
public:
    int nthUglyNumber(int n) {
        std::vector<int> dp(n + 1);
        dp[1] = 1;
        int p2 = 1, p3 = 1, p5 = 1; // 初始都从 1 开始
        for (int i = 2; i <= n; i++) {
            int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
            dp[i] = std::min(std::min(num2, num3), num5); // 取较小的那个
            if (dp[i] == num2) p2++;
            if (dp[i] == num3) p3++;
            if (dp[i] == num5) p5++;
        }
        return dp[n];
    }
};

int main(int argc, char *argv[]){
    int n = 10;
    Solution S1;
    int res = S1.nthUglyNumber(n);
    std::cout << res << std::endl;
    return 0;
}

10--第一个只出现一次的字符(50)

主要思路:

        基于无序哈希表,key为字符,value 为该字符是否唯一,遍历字符串,当字符唯一时,value 设为 true,否则设为 false;

#include <iostream>
#include <string>
#include <unordered_map>

class Solution {
public:
    char firstUniqChar(std::string s) {
        int len = s.size();
        std::unordered_map<char, bool> M;
        for(int i = 0; i < len; i++){
            if(M.find(s[i]) == M.end()){ // 没找到
                M[s[i]] = true;
            }
            else{
               M[s[i]] = false; // 找到了,表明重复置为false 
            } 
        }

        for(int i = 0; i < len; i++){
            if(M[s[i]] == true){ // 找到第一个值为 true 的字符
                return s[i];
            }
        }
        return ' ';
    }
};

int main(int argc, char *argv[]){
    std::string test = "abaccdeff";
    Solution S1;
    char res = S1.firstUniqChar(test);
    std::cout << res << std::endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值