【程序员刷题必备 | 《剑指offer》】(一)

简介: 【程序员刷题必备 | 《剑指offer》】(一)

1.滑动窗口的最大值(剑指offer原59题)


  • 解题思路:其实是一个队列的问题,用一个队列去维护当前窗口中的所有元素;首先将超出窗口中的队头元素先删掉,然后将新的元素插入当前窗口中,插入时要判断新插入的元素与队尾元素的大小,如果队尾元素较小,则先删除队尾元素再插入


#include <iostream>
#include <vector>
#include <deque>
using namespace std;
class Solution{
public:
    vector<int> maxInWindows(vector<int>& nums, int k)   // k是窗口的大小
    {
        vector<int> res;
        deque<int> q;
        for(int i = 0; i < nums.size(); i++)
        {
            while(q.size() && q.front() <= i - k) q.pop_front();  // 将已经划出窗口中的元素从队列中删除
            while(q.size() && nums[q.back()] <= nums[i])  nums.pop_back();   // 如果在队尾插入的元素大于等于当前队尾的元素值,就可以删除队尾的元素!
            q.push_back(i);
            if(i >= k - 1) res.push_back(nums[q.front()]);
        }
        return res;
    }
};


2.n个骰子的点数,这里求的是扔n次骰子,n次中骰子点数之和是s的,所有方案可能的数!(与剑指offer60题有点差别!)


  • 解题思路:每次扔骰子,最小值是1,最大值是6;所以扔n个骰子在地上后的,最小值就是n,最大值就是6*n。dfs()算法的思路:


#include <iostream>
#include <vector>
using namespace std;
// dfs方法来解决:注意两点,第一是状态的表示是什么(从输出中来)?第二是按照什么顺序来计算第一步中的状态?
class Solution{
public:
    vector<int> numberOfDice(int n){
        vector<int> res;
        for(int i = n; i <= 6 * n; i++) res.push_back(dfs(n, i));  // dfs(n, s)表示的就是所要输出的结果;也就是每次求总和是s的情况下,一共投了n次骰子,一共有多少种方案
        return res;
    }
    int dfs(int n, int sum){
        if(sum < 0) return 0;
        if(n == 0) return !sum;
        for(int i = 1; i <= 6; i++)
        {
            res += dfs(n - 1, sum - i);  // 热狗法:最后一次骰子点数已经确定时,则只需要计算前面投了n-1次骰子,总和是s-i的情况下,一共有多少种方案。
        }
        return res;
    }
};


  • 动态规划算法dp的思路:


#include <iostream>
#include <vector>
using namespace std;
// dp方法来解决:注意三点,第一是状态的表示是什么(从输出中来)?第二是如何计算第一步中的状态?第三是边界问题
class Solution{
public:
    vector<int> numberOfDice(int n){
       vector<vector<int>> f(n + 1, vector<int>(n * 6 + 1));  // dp的状态表示
       f[0][0] = 1;  // 当所有的骰子都没有扔出时,总和s=0时,只有一种方案;总和s=1, 2, 3, 4, ....都是不合法的!
       for(int i = 1; i <= n; i++){  // 先循环扔出去的次数
           for(int j = 1; j <= i * 6; j++){      // 再循环总和s是多少
                for(int k = 1; k <= min(j, 6); k++){  // 枚举最后一次的点数是多少
                    f[i][j] += f[i- 1][j - k];  // 状态f[i][j]表示前i次总和是j的方案数!
                }
           }
       }
       vector<int> res;
       for(int i = n; i <= n * 6; i++) res.push_back(f[n][i]);
       return res;
    }
};


3.扑克牌的顺子(剑指offer原61题)


  • 解题思路:模拟人的想法,先将除去了大小王之外的牌拿过来,如果有相同元素,则一定不是顺子!如果没有任何两个元素相同,看一下牌中最小值与最大值的差距是否在4以内。如果满足条件,则可以将缺失的部分用大小王来进行填补。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 从扑克牌中随机抽5张牌,判断是不是一个顺子。
class Solution{
public:
    bool isContinous(vector<int>& nums)
    {
        if(nums.empty()) return false;
        sort(nums.begin(), nums.end());
        int k = 0;
        while(!nums[k]) k++;   // 去掉行首的0
        for(int i = k + 1; i < nums.size(); i++){  // 去掉重复元素
            if(nums[i] == nums[i - 1]) return false;
        }
        return nums.back() - nums[k] <= 4;
    }
};


4.圆圈中最后剩下的数字(剑指offer原62题)----约瑟夫环问题


  • 解题思路:f(n,m)表示总共n个数字,每次报到数字m时,就将此数字从环中删除,最后剩下的数字。f(n-1,m)表示从剩下的n-1个数字中,每次报到数字m时,就将此数字从环中删除,最后剩下的数字。观察f(n,m)与f(n-1,m)之间的关系,可知f(n,m) = (f(n-1,m) + m) % n,其中边界条件是f(n==1, m) = 0;


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
public:
    int lastRemaining(int n, int m){
        if(n == 1) return 0;
        return (lastRemaining(n - 1, m) + m) % n;
    }
};


5.股票的最大利润(剑指offer原63题)


  • 解题思路:找出前i天的最小值,利用一个变量minValue来存储。第i天卖出的价格是nums[i],最大利润res是等于第i天卖出价格与前i天中价格最低时买入的价格之差,此时获得的利润是最大的。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
public:
    int maxDiff(vector<int>& nums)
    {
        if(nums.empty()) return 0;
        int res = 0;   // 最大利润
        for(int i = 1, minValue = nums[0]; i < nums.size(); i++){
            res = max(res, nums[i] - minValue);  // minValue表示前i天的最小值,nums[i]表示第i天卖出的价格!
            minValue = min(minValue, nums[i]);
        }
        return res;
    }
};


6.求1+2+3+...+n,不能使用乘除法和各种循环、判断语句(剑指offer原64题)


  • 解题思路:使用递归的思路来写,但是将当中的if语句,改成&&运算符。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
public:
    int getSum(int n){
        int res = n;
        n > 0 && (res += getSum(n - 1));   // 实际是对if(n > 0) res += getSum(n - 1);语句的改写
        return res;
    }
};


7.不用加减乘除做加法(剑指offer原65题)


  • 解题思路:模拟计算机中的加法A + B,结果是CD。其中C是十位,D是个位。A和B对应位上的取值有四种(0 0、0 1、1 0、1 1),C上的结果是(0 0 0 1),D上的结果是(0 1 1 0)。可以将C上的结果看出(A对应位上的取值 & B对应位上的取值);将D上的结果看出(A对应位上的取值 ^ B对应位上的取值)。因此,可以将多位数相加A + B可以看出是A + B= A^B(无进位) + (A & B << 1)(A & B表示的就是进位)。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
public:
    int add(int num1, int num2){
        while(num2){
            int sum = num1 ^ num2;
            int carry = (num1 & num2) << 1;
            num1 = sum;
            num2 = carry;
        }
        return num1;
    }
};


8.构建乘积数组(剑指offer原66题)[不能用除法、只能开一个数组]


  • 解题思路:B[i] = A[0] * A[1] * ... * A[i-1] * A[i+1] * ... * A[n-1]。可以先算i的左半边 A[0] * A[1] * ... * A[i-1],然后算i的右半边A[i+1] * ... * A[n-1],最后两部分相乘。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution{
public:
    vector<int> multiply(const vector<int>& A){
        if(A.empty()) return vector<int>();
        int n = A.size();
        vector<int> B(n);
        // 先算A[0]到A[i-1]的乘积
        for(int i = 0, p = 1; i < n; i++){
            B[i] = p;
            p *= A[i];
        }
        // 再算A[i+1]到A[n-1]的乘积
        for(int i = n - 1, p = 1; ~i; i--){
            B[i] *= p;
            p *= A[i];
        }
        return B;
    }
};


9.把字符串转换成整数(剑指offer原67题)


  • 解题思路:处理好各种边界问题!


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
class Solution{
public:
    int strToInt(string str)
    {
        int k = 0;
        while(k < str.size() && str[k] == ' ') k++;  // 忽略所有的行首空格!
        long long number = 0;
        bool is_minus = false;
        // 忽略完行首的空格后,可能有-/+的符号
        if(str[k] == '+') k++;
        else if(str[k] == '-') k++, is_minus = true;
        while(k < str.size() && str[k] >= '0' && str[k] <= '9'){
            number = number * 10 + str[k] - '0';   // 字符串表示的数字转换成真正的数字
            k++;
        }
        if(is_minus) number *= -1;  // 处理负数的情况
        if(number > INT_MAX) number = INT_MAX;
        if(number < INT_MIN) number = INT_MIN;
        return (int)number;
    }
};


10.树中两个结点的最低公共祖先(剑指offer原68题)


  • 解题思路:给出的两个结点的位置可能有两种情况,一种是两个结点出现在一个结点的左右两个子树上;另一种是一个给定的结点出现最低公共祖先节点上,另一个给定的结点出现在左子树或右子树上!具体的方法是:先遍历左子树,检查是否有给定的两个结点p、q;再遍历右子树,检查是否有给定的两个结点p、q。如果左右子树中同时出现了p、q,则当前结点就是需要返回的就是最低公共祖先结点;如果只在左子树或右子树中出现p、q,则返回值就是p、q的最低公共祖先。


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution{
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
        if(!root) return nullptr;  // 空树
        if(root == p || root == q) return root;
        auto left = lowestCommonAncestor(root->left, p, q);   // 检查一下左边是否有p和q
        auto right = lowestCommonAncestor(root->right, p, q);  // 检查一下右边是否有p和q
        if(left && right) return root;
        if(left) return left;
        return right;
    }
};


11.数字在排序数组中出现的次数(剑指offer原53题--题目一)


  • 解题思路:**二分法解决!**就是此数字第一次出现的位置与此数字最后一次出现的位置,两者之间的数的个数就是该数字出现的次数!


#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution{
public:
    int getNumberOfK(vector<int>& nums, int k){
        if(nums.empty()) return 0;
        int l = 0, r = nums.size() - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] < k) l = mid + 1;
            else r = mid;
        }
        if(nums[l] != k) return 0;
        int left = l;
        l = 0, r = nums.size() - 1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(nums[mid] <= k) l = mid;
            else r = mid - 1;
        }
        return r - left + 1;
    }
};


12.0到n-1中缺失的数字(剑指offer原53题---题目二)


  • 题目要求的是:长度为n的数组,将其中的一个数删掉,只剩下n-1个数了。将剩下的n-1个数作为程序的输入,找出被删除的那个数!
  • 解题思路:先计算0到n-1中的n个数的和,再减去当前序列中的每个数,也就可以得到答案了。


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
class Solution{
public:
    int getMissingNumber(vector<int>& nums){  // nums是输入的n-1个数
        int n = nums.size() + 1;
        int res = (n - 1) * n / 2;
        for(auto x: nums) res -= x;
        return res;   // res就是0到n-1中缺失的那个数字
    }
};


13.数组中数值和下标相等的元素(剑指offer原53题---题目三)


  • 解题思路:因为给定的数组nums具有单调递增的性质,可以使用二分查找,时间复杂度是O(logn)。考察数组nums[i]-i是否具有单调性。即(nums[i]-i >= nums[i-1] - (i-1)是否成立?)


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
class Solution{
public:
    int getNumberSameAsIndex(vector<int>& nums){
        int l = 0, r = nums.size() - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] - mid >= 0) r = mid;
            else l = mid + 1;
        }
        if(nums[r] - r == 0) return r;  // 相等元素的下标
        return -1;
    }
};


14.二叉搜索树的第K个大的结点(剑指offer原54题)


  • 解题思路:先对二叉搜索树进行中序遍历,每遍历到一个结点后,就对K进行减一操作。直到k减小到0后,就已经找到了第K个大的结点。


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
// 二叉树的定义
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution{
public:
    TreeNode* ans;
    TreeNode* KthNode(TreeNode* root, int k){
        dfs(root, k);
        return ans;
    }
    void dfs(TreeNode* root, int& k){
        if(!root) return;
        dfs(root->left, k);  // 中序遍历
        k--;
        if(!k) ans = root;
        if(k > 0) dfs(root->right, k);
    }
};


15.二叉树的深度(剑指offer原55题)


  • 解题思路:深度就是找出从根结点到叶子节点的路径最长长度!具体就是找出根节点的左右子树两者中更长者的深度+1,即二叉树的深度。左右子树的深度用递归的方法来求解,当递归到叶子节点时,递归停止!


#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
// 二叉树的定义
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution{
public:
    int treeDepth(TreeNode* root){
        if(!root) return 0;  // 递归终止条件!
        return max(treeDepth(root->left), treeDepth(root->right)) + 1;
    }
};


16.平衡二叉树(剑指offer原55题--题目二)


  • 解题思路:利用上一题的思路,求出左右子树的深度之差是否是大于1的,如果所有点的深度差都不大于1的话,则是平衡二叉树;如果任意一个结点的左右子树深度之差大于1,则一定是非平衡二叉树!


#include <iostream>
#include <cmath>
using namespace std;
// 二叉树的定义
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(nullptr), right(nullptr){}
};
class Solution{
public:
    bool ans = true;
    bool isBalanced(TreeNode* root){
        dfs(root);
        return ans;
    }
    int dfs(TreeNode* root){
        if(!root) return 0;
        int left = dfs(root->left), right = dfs(root->right);
        if(abs(left - right) > 1) ans = false;
        return max(left, right) + 1;  // 当前结点的深度 == 当前结点左右子树的深度的更大者 + 1
    }
};


17.数组中只出现一次的两个数字(剑指offer原56题)


  • 原题是:一个数组中除了两个数字之外,其他数字都出现了2次。利用程序找出这两个只出现一次的数字!
  • 解题思路:


#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution{
public:
    vector<int> findNumsAppearance(vector<int>& nums){
        int sum = 0;
        for(auto x : nums) sum ^= x;   // 先求所有数字的异或和,也就是sum = x ^ y  x,y分别表示数组中只出现一次的数字
        int k = 0;
        while(!(sum >> k & 1)) k++;  // 然后从sum中找出其二进制表示中任意一位不为0的位,k存储的就是x ^ y结果中第k位是1的那一位
        int first = 0;
        for(auto x : nums){
            if(x >> k & 1)   // 将x的二进制表示中第k位是1的划分到第一个集合first中!
                first ^= x;  // 第一个集合异或的结果first  第二个结果异或的结果first ^ sum
        }
        return vector<int>{first, sum ^ first};
    }
};


  • 先考虑一种简单的情况,数组中除了一个数字只出现一次外,其余数字都出现了2次,找出这个只出现一次的数字。利用异或运算的特点,所有出现两次的数字异或时都被消成0,再将异或结果与只出现一次的数字进行异或,结果就是我们要找的数字
  • 本题中只出现一次的数字有两个,如何找出这两个只出现一次的数呢?利用上面一样的操作,对所有的数字执行异或操作,得到的结果是两个只出现一次的数字的异或,由于两个数字都只出现一次。因此,最终的异或结果肯定不等于0。因为两个只出现一次的数字的异或的结果不等于0,所以异或结果的二进制表示中肯定有一位是1。假设异或结果中的第3位是1,则两个只出现一次的数字二进制表示的第3位一定是不相同的。此时,将原始数组中所有数字划分成两个集合,划分的依据就是看数组中每个数字的第3位是0还是1。因此,两个只出现一次的数字一定不在同一个集合中!所有出现两次的数字一定在同一个集合中!此时,两个集合中的数字就转化成最开始讨论的一种简单情况的例子


18.数组中唯一只出现一次的数字(剑指offer原56题--题目2)


  • 原题目:一个数组中除了一个数字只出现一次外,其他数字都出现了三次,找出那个只出现一次的数字。
  • 解题思路:有限状态机原理,初始的状态是(ones=0,twos=0);输入的数字的二进制表示中某一位是1时,状态转移成(1,0),接着数字的二进制表示中某一位仍然是1时,状态转移成(0,1),接着数字的二进制表示中某一位继续是1时,状态转移成(0,0)。也就是每三个状态构成一个循环;当输入的数字的二进制表示中某一位是0时,从初始的状态是(ones=0,twos=0)转移至自身(0,0);当所有的输入数字中某一位出现次数是%3余1时,状态就转移到(1,0);当所有的输入数字中某一位出现次数是%3余0时,状态就转移到(0,0)状态。ones就代表了上面两种情况的结果。数组中唯一只出现一次的数字。


#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution{
public:
    int findNumberAppearingOnce(vector<int>& nums){
        int ones = 0, twos = 0;
        for(auto x : nums){
            ones = (ones ^ x) & ~twos;
            twos = (twos ^ x) & ~ones;
        }
        return ones;
    }
};


19.和为s的数字(剑指offer原57题---题目一)


  • 解题思路1:暴力解法,先依次遍历每个数字,遍历到某个数字时,固定这个数字。再依次判断数组中其余的n-1个数字与它的和是否等于target。


#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 暴力解法O(n**2)
class Solution{
public:
    vector<int> findNumberWithSum(vector<int>& nums, int target){
        for(int i = 0; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[i] + nums[j] == target) return vector<int>{nums[i], nums[j]};
            }
        }
    }
};


  • 解题思路2:对第二重循环进行优化,第二重循环的目的是判断对于j < i这个范围内,是否存在一个数字nums[j]使得target - nums[i] == nums[j]成立。因此,可以使用哈希表来统计数字nums[j]是否出现从而来优化,使得时间复杂度变成O(n)。


#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
using namespace std;
class Solution{
public:
    vector<int> findNumberWithSum(vector<int>& nums, int target){
        unordered_set<int> hash;
        for(int i = 0; i < nums.size(); i++){
            if(hash.count(target - nums[i])) return vector<int>{target - nums[i], nums[i]};  // hash.count(target - nums[i])就是判断nums[j]是否在j < i的范围内出现!
            hash.insert(nums[i]);
        }
        return vector<int>();
    }
};


20.和为s的连续正数序列(剑指offer原57题---题目二)


  • 原题:输入一个正数s,输出所有和为s的连续正数序列,序列中至少含有两个数。
  • 解题思路:暴力方法是给出区间的起点i,再给出区间的终点j。利用求和公式计算出区间[i,j]中数字的和是否为s,时间复杂度是O(n**2)。改进的方法是:假设区间[i,j]中数字的和是s,当区间左端点i向右移动到i1时,区间的右端点j也会向右移动到j1,如果右端点j向左移动到j2,则区间[i1,j2]中的数字之和一定是小于s的。总结起来就是使用双指针算法,时间复杂度变成O(n)。


#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 暴力方法:O(n**2)
class Solution{
public:
   vector<vector<int>> findContinuousSequence(int sum){
       vector<vector<int>> res;
       for(int i = 1, j = 1, s = 1; i <= sum; i++)  // s是当前序列的和
       {
           while(s < sum) s += ++j;
           if(s == sum && j - i + 1 > 1){  // [i,j]中包含的元素个数是: j - i + 1
               vector<int> line;
               for(int k = i; k <= j; k++) line.push_back(k);  // line是一个一维数组,数组中存放的是区间[i,j]中和为s的数字
               res.push_back(line);
           }
           s -= i;
       }
       return res;
   }
};


21.翻转单词顺序(剑指offer原58题---题目一)


  • 原题:输入一个句子,翻转句子中单词的顺序,但每个单词内的字母顺序不变。
  • 解题思路:先用双指针i和j,将整个句子的每个单词以字母为单位进行翻转;然后对句子的每个单词进行翻转。


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution{
public:
    string reverseWords(string s){
        reverse(s.begin(), s.end());  // 等价于for(int i = 0, j = s.size() - 1; i < j; i++, j--) swap(s[i], s[j]); 第一步首先对整个句子进行翻转
        for(int i = 0; i < s.size(); i++){  // 对第一步中翻转后的每个单词进行翻转,下面是从一段字符串中提取出一个单词的操作!
            int j = i;
            while(j < s.size() && s[j] != ' ') j++;
            reverse(s.begin() + i, s.begin() + j);
            i = j;
        }
        return s;
    }
};


22.左旋转字符串(剑指offer原58题---题目二)


  • 原题是:将字符串中的前面的前n位移动到字符串的尾部。
  • 解题思路:和上一题一样的思路,先对整个字符串进行翻转。然后将翻转后的结果分成两个部分:前str.size() - n个字符和倒数n个字符,然后分别对上面的两部分进行翻转即可。


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution{
public:
    string leftRotateString(string str, int n){
        reverse(str.begin(), str.end());
        reverse(str.begin(), str.begin() + str.size() - n);
        reverse(str.begin() + str.size() - n, str.end());
        return str;
    }
};


23.数字序列中某一位的数字(剑指offer原44题)


  • 解题思路:
  • 1.确定是几位数(n - 10*1 - 90*2 - 900*3 - ...)
  • 2.确定是几位数的第几个数
  • 3.确定那个数的第几位
  • 详细过程:首先要确定第n位对应的数字在什么范围内,也就是确定第n位对应的数字是几位数。因为一位数有10个,占10位,两位数有90个,占180位,三位数有900个,占2700位。假设输入的是第1000位,则第1000位对应的应该是一个三位数(因为1000-10-180 = 720 < 2700);然后确定第1000位对应的是哪个三位数上的某一位。因为经过上一步的分析可知,输入的第1000位出现在两位数之后的第720位,因为三位数每个数占3位,所以输入的第1000位对应的应该是第240个三位数中的某一位!由于三位数从100开始,所以第240个三位数是100 + 240 - 1 = 339;最后确定对应是339中的哪一位(因为720 / 3 = 240,所以应该对应339的最后一位9)


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution{
public:
    int digitAtIndex(int n){
        long long i = 1, s = 9, base = 1;  // i是几位数  s是几位数的个数  base是几位数的开始第一个数字
        // 确定n对应是几位数
        while(n > i * s){
            n -= i * s;
            i++;
            s *= 10;
            base *= 10;
        }
        // 确定是几位数中的哪个数
        int number = base + (n + i - 1) / i - 1;
        // 确定那个数的第几位
        int r = n % i ? n % i : i;
        for(int j = 0; j < i - r; j++) number /= 10;
        return number % 10;
    }
};


24.把数组排成最小的数(剑指offer原45题)


  • 解题思路:首先在数组中定义两个数字之间的小于<关系:即a < b等价于ab < ba。然后将原始的输入数组按照定义的小于关系重新排序,一次拼接派好序后的数组中的数字即可。


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution{
public:
    static bool cmp(int a, int b){
        auto as = to_string(a), bs = to_string(b);
        return as + bs < bs + as;
    }
    string printMinNumber(vector<int>& nums){
        sort(nums.begin(), nums.end(), cmp);
        string res;
        for(auto x : nums) res += to_string(x);
        return res;
    }
};


25.把数字翻译成字符串(剑指offer原46题)


  • 解题思路:大部分计数的问题,可以看成是动态规划的问题。问题的关键是a.状态表示 b.状态如何计算 c.边界怎么定义。f(i)表示前i位数字一共有多少种翻译方式,f(i) 如何计算?如果将第i位数字单独翻译成一个字母,则f(i)可表示为前i-1位数字一共有多少种翻译方式;如果将第i位和第i-1位数字翻译成两个个字母,则f(i)可表示为前i-2为数字一共有多少种翻译方式。综合上述两种情况,f(i) = f(i-1) + f(i-2)。注意第二种情况:f(i-2)是将第i和第i-1位数字联合起来翻译成字母,因此必须有约束,范围是[10,25]之间。最后,考虑边界f(0) = 1。


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution{
public:
    int getTranslationCount(string s){
        int n = s.size();
        vector<int> f(n+1);
        f[0] = 1;
        for(int i = 1; i <= n; i++){
            f[i] = f[i - 1];   // 第一种情况
            int t = (s[i-2] - '0') * 10 + s[i-1] - '0';  // 第二种情况
            if(t >= 10 && t <= 25) f[i] += f[i-2];
        }
        return f[n];
    }
};


相关文章
|
3月前
剑指offer58 - 2刷题打卡
剑指offer58 - 2刷题打卡
18 0
|
3月前
剑指offer05刷题打卡
剑指offer05刷题打卡
21 1
|
4月前
|
算法 机器人
剑指offer刷题指南
剑指offer刷题指南
52 0
|
3月前
|
SQL 关系型数据库 MySQL
几道常见面试问题总结(二)
几道常见面试问题总结(二)
|
9月前
|
算法 程序员
程序员可能越来越排斥面试时做题的原因
程序员可能越来越排斥面试时做题的原因
33 1
|
10月前
牛客网《剑指offer》专栏刷题练习|锻炼递归思想|练习栈的使用
牛客网《剑指offer》专栏刷题练习|锻炼递归思想|练习栈的使用
65 0
|
10月前
|
算法
数据结构算法leetcode刷题练习(1)
数据结构算法leetcode刷题练习(1)
|
10月前
|
存储 JavaScript 前端开发
牛客刷题DAY3(编程题)
牛客刷题DAY3(编程题)
61 0
|
10月前
|
移动开发 前端开发 JavaScript
牛客刷题Day4
牛客刷题Day4
69 0
|
存储 分布式计算 Java
几道面试题
几道面试题
几道面试题