训练题:整数中1出现的次数

题目:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/

总结规律。

  1. //
  2. // 设数字50162,high为高位,cur为当前位,low为低位,digit为cur的数位级别
  3. // 如cur为1的位置,则high=50,low=62,digit=100(cur在百位)
  4. //
  5. // cur在个位
  6. // 个位为1的可取范围:00001 ~ 50161,high = 5016, low = 0, digit = 1, low = digit - 1
  7. // 0000 ~ 5016 = 5017 = high * digit + low + 1 = high * digit = (high + 1) * digit
  8. //
  9. // cur在十位
  10. // 十位为1的可取范围:00010 ~ 50119, high = 501, low = 9, digit = 10, low = digit - 1
  11. // 000 0 ~ 501 9 = 5020 = high * digit + low + 1 = (high + 1) * digit
  12. //
  13. // cur在百位
  14. // 百位为1的可取范围:00100 ~ 50162, high = 50, low = 62, digit = 100
  15. // 00 00 ~ 50 62 = 5063 = high * digit + low + 1
  16. //
  17. // cur在千位
  18. // 千位为1的可取范围:01000 ~ 41999, high = 5, low = 999, digit = 1000,low = digit - 1
  19. // 0 000 ~ 4 999 = 5000 = (high - 1) * digit + low + 1 = high * digit
  20. //
  21. // cur在万位
  22. // 万位为1的可取范围:10000 ~ 19999, high=0, low = 9999, digit = 10000, low = digit - 1
  23. // 0000 ~ 9999 = 10000 = high * digit + low + 1 = (high + 1) * digit
  24. //
  25. // 综上我们总结出规律,将数字拆分成 high cur low三个部分,如50162,high = 50, cur=1, low=62
  26. // 我们令此时的digit = 10,即cur的数位(个、十、百等等)
  27. // cur位为1的数字数量 = high*digit+low+1其实就是high和low拼接成新的数字,这个数字的大小。
  28. // 这是通常情况,有特殊情况:即cur位置上的数是0和1的情况。说白了就是high和low的取值有特殊情况。
  29. //
  30. // cur位置上的数=0时,count = (high-1)*digit+low+1=high*digit,此时low=digit-1。
  31. // cur位置上的数=1时,count = high*digit+low+1
  32. // 其他情况,count = high*digit+low+1=(high+1)*digit,此时low=digit-1。
  33. //
  1. int countDigitOne(int n) {
  2. int count = 0;
  3. int high, low;
  4. char cur;
  5. long digit = 1;
  6. for(int i = n; i; i /= 10, digit *= 10){
  7. high = i / 10;
  8. cur = i % 10;
  9. if(cur == 0) count += high * digit;
  10. else if(cur == 1) {
  11. low = n % digit;
  12. count += high * digit + low + 1;
  13. }
  14. else count += (high + 1) * digit;
  15. }
  16. return count;
  17. }

训练题:数据流的中位数

题目:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof

维护两个堆(大堆和小堆),设为maxHeap和minHeap。两个各保存一半的数字。取根的平均值就是中位数。
当插入第奇数个数字时,插入到小堆中。

  • 时间复杂度:
    • 插入数字:O(logn)
    • 返回中位数:O(1)
  • 空间复杂度:O(n) ```cpp

class MedianFinder { public: using MaxHeap = priority_queue, std::less>; using MinHeap = priority_queue, std::greater>;

public: /* initialize your data structure here. / MedianFinder() { }

  1. void addNum(int num) {
  2. if(maxHeap.size() == minHeap.size()){
  3. // 注意特殊情况,num本来是要插入到minHeap中,但是如果num<maxHeap.top
  4. // 表示num比左半部分的数都要小,那么要把maxHeap的根转移到minHeap中,再把num插入到maxHeap中
  5. // 这样才能保证minHeap的所有元素都不小于maxHeap。
  6. if(!maxHeap.empty() && num < maxHeap.top()) {
  7. minHeap.push(maxHeap.top());
  8. maxHeap.pop();
  9. maxHeap.push(num);
  10. }
  11. else minHeap.push(num);
  12. }
  13. else {
  14. if(num > minHeap.top()){
  15. maxHeap.push(minHeap.top());
  16. minHeap.pop();
  17. minHeap.push(num);
  18. }
  19. else maxHeap.push(num);
  20. }
  21. }
  22. double findMedian() {
  23. if(minHeap.empty()) return 0;
  24. if(maxHeap.size() == minHeap.size()) return (maxHeap.top() + minHeap.top()) / 2.0;
  25. else return minHeap.top();
  26. }

private: MaxHeap maxHeap; MinHeap minHeap; };

  1. <a name="fb2tC"></a>
  2. # 训练题:数字序列中某一位的数
  3. 题目:[https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/](https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/)
  4. ```cpp
  5. int findNthDigit( int n )
  6. {
  7. if( n < 10 ) { return n; }
  8. char bit = 2; // 第n位所在的数是几位数
  9. long time10 = 10; // time10 = pow(10, bit-1);
  10. // 1位数:0 ~ 9 共有count=10个数
  11. // 2位数:10 ~ 99 共有count=90个数
  12. // 3位数:100 ~ 999 共有count=900个数
  13. // 4位数:1000 ~ 9999 共有count=9000个数
  14. long count = 10;
  15. // 循环过后,n是剩下的不足count个的位数。比如n是600中的6的位置。
  16. // 则n = 600 - 10*2 - 90*3 = 410
  17. // count = 900
  18. // bit = 3, time10 = 100
  19. for(;; ++bit, time10 *= 10){
  20. n -= count;
  21. count = 9 * time10 * bit;
  22. if(n < count) break;
  23. }
  24. // n/bit = 410/3 = 136,即是100开始的第136个数,也就是236。
  25. // n%bit = 410%3 = 2,即是表示在236的第2位的数,即是6。
  26. return to_string(time10 + n/bit)[n % bit] - '0';
  27. }

训练题:数字组合

题目:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/

方法一:回溯算法(递归)

排列组合问题一定要想到回溯算法。

  • 时间复杂度:O(logn)
  • 空间复杂度:O(logn),栈空间,log10n
  1. int translateNum(int num)
  2. {
  3. int count = 0, tmp = 0;
  4. fuck(num, count, tmp);
  5. return count;
  6. }
  7. // 从右往左遍历(低位到高位),例如 ......ab,ab是两个数字。
  8. // 组合方法一:取b一位
  9. // 组合方法二:如果ab>=10&&ab<=25,则又可以取ab两位,这又是一种组合方法。
  10. // num=0,表示当前这一个组合方法组合完毕,组合方法数count + 1
  11. void fuck(int num, int& count, int &tmp)
  12. {
  13. if(num == 0){
  14. ++count;
  15. return;
  16. }
  17. fuck(num / 10, count, tmp);
  18. tmp = num % 100;
  19. if(tmp <= 25 && tmp >=10 ) fuck(num / 100, count, tmp);
  20. }

方法二:动态规划

算法训练_数字 - 图1

算法训练_数字 - 图2

1、跳台阶(递归)

  • 时间复杂度:O(logn)
  • 空间复杂度:O(logn)

和跳台阶同理。

  1. char tmp100 = -1;
  2. int translateNum( int num )
  3. {
  4. if(num < 10) return 1;
  5. tmp100 = num % 100;
  6. if(tmp100 <= 25 && tmp100 >=10 ){
  7. return translateNum(num/10) + translateNum(num/100);
  8. }
  9. else
  10. return translateNum(num/10);
  11. }

2、迭代

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
  1. int translateNum( int num )
  2. {
  3. if( num < 10 ) { return 1; }
  4. // counti: dp[i-1]
  5. // counti_1: dp[i-2]
  6. int counti = 1, counti_1 = 1;
  7. for(char tmp, tmp100; num; num /= 10) {
  8. tmp100 = num % 100;
  9. if( tmp100 >= 10 && tmp100 <= 25 )
  10. {
  11. tmp = counti_1;
  12. counti_1 = counti; // dp[i-2] = dp[i-1]
  13. counti = counti + tmp; // dp[i-1] = dp[i]
  14. }
  15. else counti_1 = counti; // dp[i-2] = dp[i-1]
  16. }
  17. return counti;
  18. }

训练题:1+…+n求和

题目:http://t.cn/A62pTg9Z

方法一:条件运算实现递归

  • 时间复杂度:O(n)
  • 空间复杂度:O(n) ```cpp

// n && fuck(n-1); 实现递归。 int sumNums(int n) { n && (n+= sumNums(n-1)); return n; }

  1. <a name="pFDPD"></a>
  2. ## 方法二:位移+加法=乘法
  3. ```cpp
  4. int sumNums(int n)
  5. {
  6. return multi(n, n + 1) >> 1;
  7. }
  8. // int multi(int A, int B) {
  9. // int ans = 0;
  10. // for ( ; B; B >>= 1) {
  11. // if (B & 1) {
  12. // ans += A;
  13. // }
  14. // A <<= 1;
  15. // }
  16. // return ans;
  17. // }
  18. int multi(int a, int b){
  19. int result = 0;
  20. loop(a, b, result);
  21. return result;
  22. }
  23. bool loop(int &a, int &b, int& ans){
  24. (b & 1) && (ans += a);
  25. a <<= 1;
  26. return b && loop(a, b >>= 1, ans);
  27. }

技巧总结

位移+加法实现乘法:

  1. int multi(int a, int b){
  2. int result = 0;
  3. for(; b; b >>= 1){
  4. if(b & 1) result += a;
  5. a <<= 1;
  6. }
  7. return result;
  8. }

位移+乘法实现幂:

  1. int pow(int a, int b){
  2. int result = 1;
  3. for(; b; b >>= 1){
  4. if(b & 1) result *= a;
  5. a *= a;
  6. }
  7. return result;
  8. }

逻辑运算实现条件判断:

  1. (b & 1) && (result *= a);
  2. // 等价于
  3. if( b & 1) result *= a;

递归代替循环:

  1. // 不用条件运算符实现递归。
  2. void fuck(int n){
  3. n && fuck(n - 1); // 递归n次。
  4. }

训练题:不用加减乘除做加法

题目:http://t.cn/A6VfDjhr

  1. int add(int a, int b) {
  2. // carry = (unsigned int)(a & b) << 1; // carry是a + b的最后进位
  3. // unsigned int是为考虑有符号的情况,右位移不知道怎么运算。
  4. // num = a ^ b; // num是a + b不考虑最后进位的结果
  5. // sum = carry + num; // a + b的最终结果,可以循环迭代,直到carry为0
  6. for(int tmp; tmp;) {
  7. tmp = (unsigned int)(a & b) << 1;
  8. a = a ^ b;
  9. b = tmp;
  10. }
  11. return a;
  12. }

训练题:约瑟夫环

题目:http://t.cn/A65IvXcJ

方法一:迭代

总结数学规律。

  1. // 第一轮
  2. // 0 1 2 3 4 0 1 2 3 4
  3. // 最后剩余数字在本轮的索引idx,count为本轮的元素个数(未删除前)
  4. // idx = 3, count = 5 最后剩余数字的索引:(0 + 3) % 5
  5. // 0最后剩余数字(3)在上一轮的索引(下面一个)
  6. // 3为从0开始数,第几个数开始删除,即m的值。
  7. // 5为当前轮的元素个数(删除前)
  8. //
  9. // 综上:最后剩余数字在本轮的索引 = (lastIdx + m) % count
  10. // lastIdx:最后剩余数字在上一轮的索引
  11. //
  12. // 3 4 0 1 3 4 0 1
  13. // idx = 0, count = 4 (1 + 3) % 4
  14. //
  15. // 1 3 4 1 3 4
  16. // idx = 1, count = 3 (1 + 3) % 3
  17. //
  18. // 1 3 1 3
  19. // idx = 1, count = 2 (0 + 3) % 2
  20. //
  21. // 3
  22. // idx = 0, count = 1
  23. int lastRemaining(int n, int m) {
  24. // 最后剩余数字的索引(索引和数值相同)
  25. int targetIdx = 0; // 初始值数值0就是元素个数=1的轮的索引
  26. // len:在本轮的元素个数(未删除前),同时也表示从元素个数=2的轮,往前回推到元素个数=5的轮。
  27. for(int len = 2; len <= n; ++len){
  28. // targetIdx的初始值刚好是len=2轮的lastIdx,即(targetIdx+m)中的targetIdx的值。
  29. targetIdx = (targetIdx + m ) % len;
  30. }
  31. return targetIdx;
  32. }

方法二:递归

  1. // n表示当前轮的元素个数(未删除前),如n=1,表示最后剩下一个数字的那一轮;n = 5,就是第一轮。
  2. // m表示从0开始的第几个数删除。
  3. // 返回最后剩下的数字在当前轮的索引
  4. int lastRemaining(int n, int m) {
  5. // n=1就是一个元素,返回值当然是0了。
  6. if(n == 1) return 0;
  7. 最后剩下的数字在当前轮的索引 = (该数字在下一轮的索引 + m) % n
  8. return (lastRemaining(n-1, m) + m) % n;
  9. }

训练题:判断顺子

题目:http://t.cn/A65VhbL0

  • big - small < 5,big、small为非王牌
  • 无重复牌 ```cpp

bool isStraight(vector& nums) { if(nums.size() != 5) return false;

  1. set<int> info;
  2. int big = 0; // 最大的非0牌
  3. int small = 20; // 最小的非0牌
  4. for(char i = 0; i < 5; ++i){
  5. if(nums[i]){
  6. if(info.count(nums[i])) return false; // 有重复的非0牌肯定不是顺子
  7. big = max(big, nums[i]);
  8. small = min(small, nums[i]);
  9. info.insert(nums[i]);
  10. }
  11. }
  12. return big - small < 5; // 最大非0牌和最小牌之间不能超过4

}

  1. <a name="LKmY0"></a>
  2. # 训练题:和为s的连续整数序列
  3. 题目:[http://t.cn/A67EmKAI](http://t.cn/A67EmKAI)
  4. <a name="CWEza"></a>
  5. ## 方法一:数学求极值
  6. - target为目标值。
  7. - k为连续数列的个数,k为正整数,k>=2。
  8. - n为数列的起始数值。n为正整数,且n>=1。
  9. ![](https://cdn.nlark.com/yuque/__latex/e7984c427d7d40494c25e28654b6f320.svg#card=math&code=%5CLarge%20%20%5Cbegin%7Barray%7D%7Bl%7D%0A%5Cbegin%7Barray%7D%7Bl%7D%0Atarget%20%26%3D%20n%20%2B%20%28n%2B1%29%20%2B%20...%20%2B%20%28n%2Bk-1%29%20%5C%5C%0A%26%3D%20k%5Ctimes%20n%20%2B%20%5Cfrac%7Bk%5Ctimes%20%28k-1%29%7D%7B2%7D%20%5C%5C%0A%5Cend%7Barray%7D%20%5C%5C%0A%5CLongrightarrow%20k%5E%7B2%7D%2B%282n-1%29k-2target%3D0%20%5C%5C%0A%5CLongrightarrow%20k%20%3D%20%5Cfrac%7B1-2n%2B%5Csqrt%7B%282n-1%29%5E2%2B8target%7D%7D%7B2%7D%20%5C%5C%0A%0A%5CLongrightarrow%20%E4%BB%A4t%3D2n-1%5Cge1%EF%BC%8Cq%3D8target%5Cge24%EF%BC%8C%E6%9C%89k%20%3D%20%5Cfrac%7B%5Csqrt%7Bt%5E2%2Bq%7D-t%7D%7B2%7D%EF%BC%8Ct%E8%B6%8A%E5%A4%A7%EF%BC%8Ck%E8%B6%8A%E5%B0%8F%EF%BC%8C%5C%5C%E6%97%A0%E9%99%90%E6%8E%A5%E8%BF%910%EF%BC%8Ct%E5%8F%96%E6%9C%80%E5%B0%8F%E5%80%BC%E6%97%B6%EF%BC%8Ck%E6%9C%80%E5%A4%A7%E3%80%82%5C%5C%0A%5CLongrightarrow%20t%3D1%E6%97%B6%EF%BC%8Ck%3D%5Cfrac%7B%5Csqrt%7B8target%2B1%7D-1%7D%7B2%7D%E6%97%B6%EF%BC%8C%E6%95%B0%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%E6%9C%80%E9%95%BF%E4%B8%94%E9%A6%96%E4%BD%8D%E6%95%B0%E5%AD%97%E6%9C%80%E5%B0%8F%E3%80%82%5C%5C%0A%E5%BD%93%E7%84%B6k%E5%BF%85%E9%A1%BB%E8%A6%81%E6%98%AF%E6%95%B4%E6%95%B0%E3%80%82%0A%5Cend%7Barray%7D&height=347&id=T93Aw)
  10. ```cpp
  11. vector<vector<int>> findContinuousSequence(int target) {
  12. vector<vector<int>> ret;
  13. // k为数列的长度。
  14. // 上面推导出了k的极值
  15. for(int k = (sqrt(1 + 8*target) - 1)/2, tmp; k >= 2; --k)
  16. {
  17. // 目标整数序列:n n+1 ... n+k-1,其中n>=1,k>=2,target为数列和
  18. // => k*n + k(k-1)/2 = target
  19. // => k*n = target - k(k-1)/2,令tmp=target - k(k-1)/2
  20. // => k*n = tmp;
  21. // => n = tmp / k,因为n是整数,所以tmp能整数k时,则该k值为合适值
  22. tmp = target - (k*(k-1)>>1);
  23. if(tmp < k) break; // 边界条件,n = tmp / k >= 1 => tmp >=k
  24. if(!(tmp % k)){ // 判断是否能整数
  25. ret.push_back({});
  26. tmp /= k;
  27. for(int m = tmp; m < (tmp + k); ++m) ret.back().push_back(m);
  28. }
  29. }
  30. return ret;
  31. }

方法二:双指针

  • left和right为某个序列的首尾索引。
  • 初始,left=1,right=2。
  • 当这个序列和小于目标值,就将right右移一位,使得序列值增大;反之,则将序列的left右移一位,使序列值减小。
  • left必须小于right ```cpp

vector> findContinuousSequence(int target) {

  1. vector<vector<int>> ret;
  2. int left = 1, right = 2, sum;
  3. for(int left = 1, right = 2, sum; left < right;) {
  4. sum = (left + right)*(right - left + 1)/2;
  5. if(sum == target){
  6. ret.emplace_back();
  7. for(int i = left; i <= right; ++i) ret.back().push_back(i);
  8. }
  9. if(sum < target) ++right;
  10. else ++left;
  11. }
  12. return ret;

}

  1. <a name="UQvRD"></a>
  2. # 训练题:打印1到n(全排列)
  3. 题目:[http://t.cn/A6zxsZ55](http://t.cn/A6zxsZ55)
  4. 排列组合想到回溯算法。
  5. <a name="a8yub"></a>
  6. ## 方法一
  7. 递归过程中记录了当前是第几位的数。
  8. ```cpp
  9. vector<int> printNumbers( int n )
  10. {
  11. vector<int> ret;
  12. string digitNum = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', };
  13. string tmpNum( n, '\0' );
  14. ret.reserve( pow( 10, n ) );
  15. _printNumbers( ret, digitNum, n, tmpNum, 0 );
  16. return ret;
  17. }
  18. // container:全排列应该就有10的n次方个。
  19. // digitNum:10个阿拉伯数字字符。
  20. // maxBits
  21. void _printNumbers( vector<int> &container, const string &digitNum, const int &maxBits, string &tmpNum, int idx )
  22. {
  23. if( idx == maxBits )
  24. {
  25. int tmp = atoi( tmpNum.c_str() );
  26. if( tmp ) { container.push_back( tmp ); }
  27. return;
  28. }
  29. for( auto &c : digitNum )
  30. {
  31. tmpNum[idx] = c;
  32. _printNumbers( container, digitNum, maxBits, tmpNum, idx + 1 );
  33. }
  34. }

方法二

递归过程不记录第几位的数,而是在string的结尾push_back/pop_back来调整。

  1. vector<int> printNumbers( int n )
  2. {
  3. vector<int> ret;
  4. string digitNum = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', };
  5. string tmp;
  6. bool flag = true;
  7. _printNumbers( ret, digitNum, tmp, n, flag );
  8. return ret;
  9. }
  10. void _printNumbers( vector<int> &container, const string &digitNum, string &tmp, const int &n, bool &flag )
  11. {
  12. if( tmp.size() == n )
  13. {
  14. if( flag ) { flag = false; }
  15. else { container.push_back( atoi( tmp.c_str() ) ); }
  16. return;
  17. }
  18. for( auto &c : digitNum )
  19. {
  20. tmp.push_back( c );
  21. _printNumbers( container, digitNum, tmp, n, flag );
  22. tmp.pop_back();
  23. }
  24. }

训练题:二进制中1的个数

题目:http://t.cn/A6bsUaDp

方法一

  1. int hammingWeight(uint32_t n)
  2. {
  3. int shit = 0;
  4. for(; n; n >>= 1) {
  5. if(n & 0x1) ++shit;
  6. }
  7. return shit;
  8. }

方法二

  1. int hammingWeight(uint32_t n) {
  2. int shit = 0;
  3. for(; n; ++shit) n &= (n-1); // 让n最右边的1变为0
  4. return shit;
  5. }

训练题:剪绳子

场景一

题目:http://t.cn/A6bsLCIz

最先考虑剪成3,剩下的考虑剪成2。

  1. int cuttingRope(int n) {
  2. if( n <=3 ) { return n - 1; }
  3. int a = n / 3, b = n % 3;
  4. if(b == 0) return pow(3, a); // 刚好可以剪成长度为3的绳子若干
  5. if(b == 1) return pow(3, a - 1) * 4; // 最后一段绳子长度是4,这时候要剪成2、2而不是1、3
  6. return pow(3, a) * 2; // 最后一段绳子长度是5,剪成3、2
  7. }

场景二

题目:http://t.cn/A6VGcavA

  1. int cuttingRope( int n )
  2. {
  3. if( n <= 3 ) { return n - 1; }
  4. int a = n / 3 - 1;
  5. int b = n % 3;
  6. int base = 1000000007;
  7. long result = 1; // 必须声明long型
  8. long contribute = 3; // 必须声明long型
  9. // 快速求幂法
  10. while( a )
  11. {
  12. if( a & 0x1 ) { result = ( result * contribute ) % base; }
  13. contribute = ( contribute * contribute ) % base;
  14. a >>= 1;
  15. }
  16. if( b == 0 ) { return ( 3 * result ) % base; }
  17. if( b == 1 ) { return ( 4 * result ) % base; }
  18. return ( 6 * result ) % base;
  19. }

训练题:幂函数

题目:http://t.cn/A6AEM40Q

  1. double myPow( double x, int n )
  2. {
  3. // 为了处理负数次方,我们会考虑去相反数
  4. n = n < 0 ? -n : n; // 值溢出的情况,因为有符号整形的整数与负数可能是不对称的。
  5. }

方法一:递归

  1. double myPow( double x, int n )
  2. {
  3. if( n == 0 || x == 1 || x == 0 ) { return 1; }
  4. return n < 0 ? ( 1 / _myPow( x, n ) ) : _myPow( x, n );
  5. }
  6. double _myPow( double x, int n )
  7. {
  8. if( n == 0 ) { return 1; }
  9. double result = _myPow( x, n / 2 );
  10. return ( ( n & 0x1 == 1 ) ? x : 1 ) * result * result;
  11. }

方法二:迭代

既然可以递归,当然就可以用迭代,时间复杂度一样,但是空间复杂度更少。难点在于对递归过程的逆解析。

  1. double myPow(double x, int n) {
  2. if( x == 0 || x == 1 || n == 0 ) { return 1; }
  3. double ret = 1;
  4. for( int shit = n; shit; shit /= 2) // 不能用位移,因为负数的右移是系统相关。
  5. {
  6. if( shit & 0x1 ) { ret *= x; }
  7. x *= x;
  8. }
  9. return n > 0 ? ret : ( 1 / ret );
  10. }

训练题:大数加法

情形一:非负数之和

题目:http://t.cn/A6VAvvjj

方法一:栈

从尾部往前逐位相加,结果保存在栈中。

  1. string solve(string s, string t) {
  2. stack<char> stk;
  3. int carry = 0;
  4. for( int i = s.size() - 1, j = t.size() - 1, tmpNum = 0; i >= 0 || j >= 0; --i, --j )
  5. {
  6. if( j >= 0 && i < 0 )
  7. {
  8. tmpNum = t[j] - '0' + carry;
  9. carry = tmpNum / 10;
  10. stk.push( tmpNum % 10 );
  11. }
  12. else if( i >= 0 && j < 0 )
  13. {
  14. tmpNum = s[i] - '0' + carry;
  15. carry = tmpNum / 10;
  16. stk.push( tmpNum % 10 );
  17. }
  18. else
  19. {
  20. tmpNum = s[i] - '0' + t[j] - '0' + carry;
  21. carry = tmpNum / 10;
  22. stk.push( tmpNum % 10 );
  23. }
  24. }
  25. if( carry ) { stk.push( carry ); }
  26. if( stk.empty() ) { return "0"; }
  27. string ret;
  28. ret.reserve( stk.size() );
  29. for( ; !stk.empty(); stk.pop() ) { ret.append( 1, stk.top() + '0' ); }
  30. return ret;
  31. }

方法二:不用栈

将结果直接保存在更长的那个字符串中,省去了栈空间。

  1. string solve(string s, string t) {
  2. int s_size = s.size();
  3. int t_size = t.size();
  4. int carry = 0;
  5. string* bigStr = &t;
  6. string* smalllStr = &t;
  7. if( s_size > t_size ) { bigStr = &s; }
  8. else { smalllStr = &s; }
  9. for( int bigIdx = std::max( s_size - 1, t_size - 1 ), smallIdx = std::min( s_size - 1, t_size - 1 ), tmpNum = 0;
  10. bigIdx >= 0 || smallIdx >= 0;
  11. --bigIdx, --smallIdx )
  12. {
  13. if( smallIdx < 0 )
  14. {
  15. tmpNum = ( *bigStr )[bigIdx] - '0' + carry;
  16. }
  17. else
  18. {
  19. tmpNum = ( *smalllStr )[smallIdx] - '0' + ( *bigStr )[bigIdx] - '0' + carry;
  20. }
  21. carry = tmpNum / 10;
  22. ( *bigStr )[bigIdx] = tmpNum % 10 + '0';
  23. }
  24. if( carry ) { bigStr->insert( 0, 1, '1' ); }
  25. return *bigStr;
  26. }

训练题:整数反转

题目:http://t.cn/EK0cAVF

  1. int reverse( int x )
  2. {
  3. int newNum = 0;
  4. // const int int_max = 2147483647; // 最小的数
  5. // const int int_min = -2147483648; // 最大的数
  6. for( int mod = x % 10; x; x /= 10, mod = x % 10 )
  7. {
  8. if( ( newNum > 214748364 ) || ( newNum == 214748364 && mod > 7 ) ) { return 0; }
  9. if( ( newNum < -214748364 ) || ( newNum <= -214748364 && mod < -8 ) ) { return 0; }
  10. // 这里的*10和 + mod都可能出现溢出,因此在这之前需提前判断。
  11. // 溢出情况1:*10溢出,触发条件:newNum比最数对应位置要大/小。
  12. // 溢出情况2:+mod溢出,触发条件:newNum等于最数对应位置数,且加了mod超出最数个位值。
  13. newNum = newNum * 10 + mod;
  14. }
  15. return newNum;
  16. }

训练题:回文数字

题目:http://t.cn/EyoTHEr

方法一

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  1. // 先遍历出所有数字到一个数组中
  2. // 然后从回文的中心位置向外扩展,依次判断是否为回文。
  3. bool isPalindrome(int x) {
  4. if( x < 0 || ( x != 0 && x % 10 == 0 ) ) { return false; }
  5. if( !( x / 10 ) ) { return true; }
  6. char num[10] = {0};
  7. char flag[2] = { 0, 1 };
  8. char maxIdx = -1;
  9. while( x )
  10. {
  11. ++maxIdx;
  12. num[maxIdx] = x % 10;
  13. x /= 10;
  14. }
  15. char left = ( maxIdx >> 1 )+1;
  16. char right = ( maxIdx >> 1 ) + flag[maxIdx & 0x1] - 1;
  17. while( --left >= 0 && ++right <= maxIdx ) if( num[left] != num[right] ) { return false; }
  18. return true;
  19. }

方法二

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) ```cpp

// rNum为翻转后的数,x为剩余的数 // 当x < rNum时,翻转过半,这个时候我们判断rNum是否等于x即可判断是否会回文。

bool isPalindrome(int x) { if(x < 0) return false; // 负数不是回文数 if(x < 10) return true; // 一位数是回文数 if(!(x % 10)) return false; // 个位为0的数不是回文数

  1. int tmp = 0;
  2. while(tmp < x){
  3. tmp = tmp * 10 + x % 10;
  4. x /= 10;
  5. }
  6. // 情况一:翻转后的数 == 剩余的数,说明是回文,这种情况必须位数是偶数
  7. // 情况二:位数为奇数时,翻转后的数要比剩余的数多1位,而这个位就是回文的中心,应跳过它。
  8. return ( tmp == x ) || ( x == tmp / 10 );

}

```