算法(十七)数组之数字

leetcode

9. 回文数

题目

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:
输入:x = 121
输出:true

示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。

题解

直接上代码:

class Solution {
public:
    bool isPalindrome(int x) {
        // 特殊情况:
        // 如上所述,当 x < 0 时,x 不是回文数。
        // 同样地,如果数字的最后一位是 0,为了使该数字为回文,
        // 则其第一位数字也应该是 0
        // 只有 0 满足这一属性
        if (x < 0 || (x % 10 == 0 && x != 0)) {
            return false;
        }

        int revertedNumber = 0;
        while (x > revertedNumber) {
            revertedNumber = revertedNumber * 10 + x % 10;
            x /= 10;
        }

        // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
        // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
        // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
        return x == revertedNumber || x == revertedNumber / 10;
    }
};

复杂度

时间复杂度: O ( l o g n ) O(logn) O(logn),每次都除以10
空间复杂度: O ( 1 ) O(1) O(1)

关键点

分奇偶考虑问题

41. 缺失的第一个正数

题目

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?

示例 1:
输入:nums = [1,2,0]
输出:3

示例 2:
输入:nums = [3,4,-1,1]
输出:2

示例 3:
输入:nums = [7,8,9,11,12]
输出:1

题解

核心思想:翻转数组元素作为下标对应的元素,之后找到没有被翻转过元素中对应的元素最小下标值+1,即为目标。

但这种算法的问题在于没有考虑数组中出现负数,解决办法为将数组中负数直接赋值为n+1。这样能够很好地兼容算法核心思想,因为取值为n+1的数组元素,不可能翻转数组中原有元素,进而不会影响到原有数组元素的翻转逻辑,示例代码如下所示:
在这里插入图片描述

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int length = nums.size();
        for (int i = 0; i < length; ++i) {
            if (nums[i] <= 0) {
                nums[i] = length + 1;
            }
        }
        
        for (int i = 0; i < length; ++i) {
            int index = abs(nums[i]) - 1;
            if (index <= length - 1) {
                // 防止重复的元素负负为正
                nums[index] = -abs(nums[index]);
            }
        }

        for (int i = 0; i < length; ++i) {
            if (nums[i] > 0) {
                return i + 1;
                break;
            }
        }

        return length + 1;
    }
};

复杂度

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

关键点

需要考虑到数组内出现负数的情况

[hot] 42. 接雨水

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

题解

定义两个数组L和R,L[i]的含义为从i向左看高度的最大值,R[i]的含义为从i向右看高度的最大值,这样min(L[i], R[i]) - height[i]即为i位置贡献的雨水的量,原理在上图已经给出。示例代码如下所示:

class Solution {
public:
    int trap(vector<int>& height) {
        int length = height.size();
        vector<int> L(length, height[0]);
        vector<int> R(length, height[length - 1]);

        for (int i = 1; i < length; ++i) {
            L[i] = max(height[i], L[i -1]);
        }
        for (int i = length - 2; i >=0; --i) {
            R[i] = max(height[i], R[i + 1]);
        }

        int res = 0;
        for (int i = 0; i < length; ++i) {
            res += min(L[i], R[i]) - height[i];
        }

        return res;
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

题解2

单调栈直接找水洼,示例代码如下所示:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        stack<int> stk;
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            while (!stk.empty() && height[i] > height[stk.top()]) {
                int top = stk.top();
                stk.pop();
                if (stk.empty()) {
                    break;
                }
                int left = stk.top();
                int width = i - left - 1;
                int h = min(height[left], height[i]) - height[top];
                ans += width * h;
            }
            stk.push(i);
        }

        return ans;
    }
};

复杂度

时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)

题解3

双指针寻找水洼,示例代码如下所示:

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int left = 0, right = height.size() - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = max(leftMax, height[left]);
            rightMax = max(rightMax, height[right]);
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;
            } else {
                ans += rightMax - height[right];
                --right;
            }
        }

        return ans;
    }
};

复杂度

时间: O ( n ) O(n) O(n)
空间: O ( 1 ) O(1) O(1)

[hot] 136. 只出现一次的数字

题目

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :
输入:nums = [2,2,1]
输出:1

示例 2 :
输入:nums = [4,1,2,1,2]
输出:4

示例 3 :
输入:nums = [1]
输出:1

题解

异或运算解决问题,示例代码如下所示:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for (const auto& n: nums) {
            res ^= n;
        }

        return res;
    }
};

复杂度

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

[hot] 238. 除自身以外数组的乘积

题目

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:
输入: [1,2,3,4]
输出: [24,12,8,6]

提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

题解

两遍遍历数组,将每一遍的中间结果记录在L和R中,最后将两个数组的取值乘在一起得到最终结果,示例代码如下所示:

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int length = nums.size();

        // L[i]存储[0, i-1]的累乘结果
        vector<int> L(length, 1); 
        // R[i]存储[i+1, length-1]的累乘结果
        vector<int> R(length, 1); 

        for (int i = 1; i < length; ++i) {
            L[i] = L[i - 1] * nums[i - 1];
        }

        for (int i = length - 2; i >= 0; --i) {
            R[i] = R[i + 1] * nums[i + 1];
        }

		vector<int> result;
        for (int i = 0; i < length; ++i) {
            result.emplace_back(L[i] * R[i]);
        }

        return result;
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

题解2

常数空间复杂度,示例代码如下:

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int length = nums.size();
        vector<int> res(length, 0);

        res[0] = 1;
        for (int i = 1; i < length; ++i) {
            res[i] = res[i - 1] * nums[i - 1];
        }
        
        int R = 1;
        for (int i = length - 1; i >= 0; --i) {
            res[i] = res[i] * R;
            R *= nums[i];
        }

        return res;
    }
};

268. 丢失的数字

题目

给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?

示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

题解

本题有多种解决办法,例如hash表等,这里提供数学解法。因为数组的范围是[0,n]的n个数,这样缺失的数字一定在这个范围内,因而可以计算n个数字期望和以及当前数组的和,相减即可得到缺失的数字,示例代码如下所示:

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int length = nums.size();
        int expect_sum = length * (length + 1) / 2;
        int actual_sum = accumulate(nums.begin(), nums.end(), 0);
        
        return expect_sum - actual_sum;
    }
};

复杂度

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

[hot] 287. 寻找重复数

题目

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:
输入:nums = [1,3,4,2,2]
输出:2

示例 2:
输入:nums = [3,1,3,4,2]
输出:3

题解

直接复刻<找出数组中重复的数字>的做法,示例代码如下:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        for (int i = 0; i < nums.size(); ++i) {
            nums[i] -= 1;
        }

        int i = 0;
        while (i < nums.size()) {
            if (nums[i] == i) {
                ++i;
                continue;
            }
            if (nums[i] == nums[nums[i]]) {
                return nums[i] + 1;
            }
            swap(nums[i], nums[nums[i]]);
        }

        return -1; 
    }
};

复杂度

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

题解2

快慢指针解决问题,示例代码如下所示,思路 比较难理解。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);
        slow = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
};

复杂度

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

[hot] 295. 数据流的中位数

题目

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值例如

[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

题解

定义小根堆和大根堆,满足如下两个条件:

  1. 小根堆存放数组较大一半元素,大根堆存放数组较小一半元素,即小根堆中每个元素都比大根堆大。
  2. 数组元素个数为奇数时,大根堆元素个数比小根堆多1;数组元素个数为偶数时,两个堆的元素个数相同。

具体实现逻辑的代码实现如下所示:

// #include <iostream>
// #include <queue>
// #include <algorithm>
// using namespace std;

class MedianFinder {
public:
    MedianFinder() {}
    
    void addNum(int num) {
        // 如果min_q和max_q size相同,则一定先push min_q,然后再push max_q
        // 这样才能保证max_q的size永远都会小于等于min_q
        if (min_q.size() == max_q.size()) {
            min_q.push(num);
            max_q.push(min_q.top());
            min_q.pop();
        } else {
            max_q.push(num);
            min_q.push(max_q.top());
            max_q.pop();
        }
    }
    
    double findMedian() {
        if (max_q.size() == min_q.size()) {
        	// 一定注意返回值类型
            return double(max_q.top() + min_q.top()) / 2;
        }

        return max_q.top();
    }
private:
    priority_queue<int> max_q; // 大根堆
    priority_queue<int, vector<int>, greater<int>> min_q; // 小根堆
}; 

// 只要能保证max_q和min_q的大小差一,先push谁都无所谓
class MedianFinder {
public:
    MedianFinder() {}
    
    void addNum(int num) {
        if (min_q.size() == max_q.size()) {
            max_q.push(num);
            min_q.push(max_q.top());
            max_q.pop();
        } else {
            min_q.push(num);
            max_q.push(min_q.top());
            min_q.pop();
        }
    }
    
    double findMedian() {
        if (min_q.size() == max_q.size()) {
            return double(min_q.top() + max_q.top()) / 2.0;
        }
        return double(min_q.top());
    }
private:
    priority_queue<int> max_q; 
    priority_queue<int, vector<int>, greater<int>> min_q;
}; 

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

复杂度

时间复杂度: O ( l o g n ) O(logn) O(logn),维护堆的时间复杂度
空间复杂度: O ( n ) O(n) O(n),优先级队列

题解2

有序集合解决问题,示例代码如下所示

#include <set>
class MedianFinder {
    multiset<int> nums;
    multiset<int>::iterator left, right;

public:
    MedianFinder() : left(nums.end()), right(nums.end()) {}

    void addNum(int num) {
        const size_t n = nums.size();

        nums.insert(num);
        if (!n) {
            left = right = nums.begin();
        } else if (n & 1) {
            if (num < *left) {
                left--;
            } else {
                right++;
            }
        } else {
            if (num > *left && num < *right) {
                left++;
                right--;
            } else {
                if (num >= *right) {
                    left++;
                } else {
                    right--;
                }
                left = right;
            }
        }
    }

    double findMedian() {
        return (*left + *right) / 2.0;
    }
};

复杂度

时间复杂度: O ( l o g n ) O(logn) O(logn),维护堆的时间复杂度
空间复杂度: O ( n ) O(n) O(n),有序集合

[hot] 338. 比特位计数

题目

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

示例 1:
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10

示例 2:
输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

题解

x=x & (x−1) 循环调用,获取二进制中1的位数,示例代码如下所示:

class Solution {
public:
    int core(int x) {
        int res = 0;
        while (x > 0) {
            x &= x - 1;
            ++res;
        }
        return res;
    }

    vector<int> countBits(int n) {
        vector<int> res;
        for (int i = 0; i <= n; ++i) {
            res.emplace_back(core(i));
        }

        return res;
    }
};

复杂度

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),每个数字二进制的1的个数不超过logn
空间复杂度: O ( 1 ) O(1) O(1)

题解2

直接动态规划解决问题,示例代码如下所示:

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> bits(n + 1);
        for (int i = 1; i <= n; i++) {
            bits[i] = bits[i & (i - 1)] + 1;
        }
        return bits;
    }
};

复杂度

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

349. 两个数组的交集

题目

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

题解

哈希表和排序两种策略,示例代码如下:

// 时间复杂度为O(m+n),空间复杂度为O(m+n)
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> set1, set2;
        for (auto& num : nums1) {
            set1.insert(num);
        }
        for (auto& num : nums2) {
            set2.insert(num);
        }
        return getIntersection(set1, set2);
    }

    vector<int> getIntersection(unordered_set<int>& set1, unordered_set<int>& set2) {
        if (set1.size() > set2.size()) {
            return getIntersection(set2, set1);
        }
        vector<int> intersection;
        for (auto& num : set1) {
            if (set2.count(num)) {
                intersection.push_back(num);
            }
        }
        return intersection;
    }
};

// 时间复杂度为O(mlogm+nlogn),空间复杂度取决于排序引入的空间复杂度
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        int length1 = nums1.size(), length2 = nums2.size();
        int index1 = 0, index2 = 0;
        vector<int> intersection;
        while (index1 < length1 && index2 < length2) {
            int num1 = nums1[index1], num2 = nums2[index2];
            if (num1 == num2) {
                // 保证加入元素的唯一性
                if (!intersection.size() || num1 != intersection.back()) {
                    intersection.push_back(num1);
                }
                index1++;
                index2++;
            } else if (num1 < num2) {
                index1++;
            } else {
                index2++;
            }
        }
        return intersection;
    }
};

350. 两个数组的交集 II

题目

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

提示:

1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000

题解

哈希表解决问题,而且交集中重复元素也会写入结果中,图示如下,
在这里插入图片描述实例代码如下所示:

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) {
            return intersect(nums2, nums1);
        }

        unordered_map<int, int> m;
        vector<int> res;

        for (auto& elem: nums1) {
            ++m[elem];
        }

        for (auto& elem: nums2) {
            if (m.count(elem) > 0) {
                res.emplace_back(elem);
                --m[elem];
                if (m[elem] == 0) {
                    m.erase(elem);
                }
            }
        }

        return res;
    }
};

复杂度

时间复杂度: O ( m + n ) O(m + n) O(m+n),遍历nums1和nums2的时间复杂度
空间复杂度: O ( m i n ( m , n ) ) O(min(m, n)) O(min(m,n)),哈希表的空间大小

题解2

不借助哈希表,直接排序,示例代码如下所示:

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        int length1 = nums1.size(), length2 = nums2.size();
        vector<int> intersection;
        int index1 = 0, index2 = 0;
        while (index1 < length1 && index2 < length2) {
            if (nums1[index1] < nums2[index2]) {
                index1++;
            } else if (nums1[index1] > nums2[index2]) {
                index2++;
            } else {
                intersection.push_back(nums1[index1]);
                index1++;
                index2++;
            }
        }
        return intersection;
    }
};

复杂度

时间复杂度: O ( m l o g m + n l o g n ) O(mlogm + nlogn) O(mlogm+nlogn),排序消耗的时间
空间复杂度: O ( 1 ) ) O(1)) O(1)),如果不算输出结果的空间

[hot] 448. 找到所有数组中消失的数字

题目

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。

示例:
输入: [4,3,2,7,8,2,3,1]
输出: [5,6]

题解

鸽笼原理,和<41. 缺失的第一个正数>的原理一样,遍历nums元素nums[i],把位置为nums[i]的元素进行正负翻转,现象为缺失数字位置的元素没有机会被翻转。再次遍历nums,找到那些没有被翻转的元素再+1即为最终结果。示例代码如下所示:

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        int length = nums.size();
        for (int i = 0; i < length; ++i) {
            int index = abs(nums[i]) - 1;
            nums[index] = -abs(nums[index]);
        }

        vector<int> res;
        for (int i = 0; i < length; ++i) {
            if (nums[i] > 0) {
                res.emplace_back(i + 1);
            }
        }

        return res;
    }
};

复杂度

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

509. 斐波那契数

题目

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。

示例 1:
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

题解

按照斐波那契数列的推导过程,进行结果的计算,示例代码如下:

class Solution {
public:
    int fib(int n) {
        if (n < 2) {
            return n;
        }

        int n1 = 0;
        int n2 = 1;
        int res = 1;

        for (int i = 2; i <= n; ++i) {
            res = n1 + n2;
            n1 = n2;
            n2 = res;
        }

        return res;
    }
};

复杂度

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

题解2

矩阵快速幂方法,具体解释可以见这里

在这里插入图片描述
示例代码如下所示,

class Solution {
public:
    int fib(int n) {
        if (n < 2) {
            return n;
        }
        vector<vector<int>> q{{1, 1}, {1, 0}};
        vector<vector<int>> res = matrix_pow(q, n - 1);
        return res[0][0];
    }

    vector<vector<int>> matrix_pow(vector<vector<int>>& a, int n) {
        vector<vector<int>> ret{{1, 0}, {0, 1}};
        while (n > 0) {
            if (n & 1) {
                ret = matrix_multiply(ret, a);
            }
            n >>= 1;
            a = matrix_multiply(a, a);
        }
        return ret;
    }

    vector<vector<int>> matrix_multiply(vector<vector<int>>& a, vector<vector<int>>& b) {
        vector<vector<int>> c{{0, 0}, {0, 0}};
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }
};

复杂度

时间复杂度: O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)

665. 非递减数列

题目

给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。

示例 2:
输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。

题解

一图抵千言,两种情况如下所示:

示例代码如下所示:

class Solution {
public:
    bool checkPossibility(vector<int>& nums) {
        int length = nums.size();
        int minus_count = 0;
        for (int i = 1; i < length; ++i) {
            if (nums[i] < nums[i - 1]) {
            	// 当nums[i] == nums[i - 2]时,要把nums[i - 1]打压下去
                if (i == 1 || nums[i] >= nums[i - 2]) {
                    nums[i - 1] = nums[i];
                } else {
                    nums[i] = nums[i - 1];
                }
                ++minus_count;
            }
            if (minus_count > 1) {
                return false;
            }
        }

        return true;
    }
};

复杂度

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

697. 数组的度

题目

给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:
输入:[1, 2, 2, 3, 1]
输出:2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

示例 2:
输入:[1,2,2,3,1,4,2]
输出:6

题解

正常思路解决问题,

class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        unordered_map<int, vector<int>> m;
        for (int i = 0; i < nums.size(); ++i) {
            int elem = nums[i];
            if (m.count(elem) > 0) {
                m[elem][0]++;
                m[elem][2] = i;
            } else {
                m[elem] = {1, i, i};
            }
        }

        int max_num = -1;
        int min_len = 0;
        for (auto& elem: m) {
            auto& vec = elem.second;
            if (vec[0] > max_num) {
                max_num = vec[0];
                min_len = vec[2] - vec[1] + 1;
            } else if (vec[0] == max_num) {
                min_len = min(vec[2] - vec[1] + 1, min_len);
            }
        }

        return min_len;
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

724. 寻找数组的中心下标

题目

给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。

注意:中心下标可能出现在数组的两端。

题解

边遍历边记录元素左侧元素之和,示例代码如下所示:

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int total = accumulate(nums.begin(), nums.end(), 0);
        int sum = 0;
        for (int i = 0; i < (int)nums.size(); ++i) {
            if (2 * sum + nums[i] == total) {
                return i;
            }

            sum += nums[i];
        }

        return -1;
    }
};

复杂度

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

746. 使用最小花费爬楼梯

题目

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

示例 1:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。

示例 2:
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

题解

类似斐波那契数列,用prev、cur、next三个数字来模拟动态规划的更新过程,示例代码如下所示:

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int prev = 0; // i - 2阶台阶的最小体力值
        int cur = 0; // i - 1阶台阶的最小体力值
        // 注意for循环的终止条件是i <= cost.size(),因为当i == cost.size()时,
        // next(or cur)才是最终目标值
        for (int i = 2; i <= cost.size(); ++i) {
            int next = min(prev + cost[i - 2], cur + cost[i - 1]);
            prev = cur;
            cur = next;
        }

        return cur;
    }
};

复杂度

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

1365. 有多少小于当前数字的数字

题目

给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。以数组形式返回答案。

提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100

示例 1:
输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
解释: 
对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。 
对于 nums[1]=1 不存在比它小的数字。
对于 nums[2]=2 存在一个比它小的数字:(1)。 
对于 nums[3]=2 存在一个比它小的数字:(1)。 
对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。

示例 2:
输入:nums = [6,5,4,8]
输出:[2,1,0,3]

示例 3:
输入:nums = [7,7,7,7]
输出:[0,0,0,0]

题解

由于限制了nums[i]的取值范围,因而可以通过计数排序的方式进行求解,示例代码如下所示:

class Solution {
public:
    vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
        vector<int> cnt(101, 0);
        for (const auto& elem: nums) {
            ++cnt[elem];
        }

        // cnt[i]记录的是,数组nums中<=i的元素个数
        for (int i = 1; i <= 100; ++i) {
            cnt[i] += cnt[i - 1];
        }

        vector<int> ret;
        for (int i = 0; i < (int)nums.size(); ++i) {
            // cnt[nums[i] - 1]才是数组中小于nums[i]的元素个数
            int freq = nums[i] == 0 ? 0 : cnt[nums[i] - 1];
            ret.emplace_back(freq);
        }
        
        return ret;
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

剑指offer

3. 找出数组中重复的数字

题目

在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了, 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。

题解

在这里插入图片描述
示例代码如下:

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int i = 0;
        while (i < nums.size()) {
            if (nums[i] == i) {
                ++i;
                continue;
            }
            if (nums[i] == nums[nums[i]]) {
                return nums[i];
            }
            swap(nums[i], nums[nums[i]]);
        }

        return -1;
    }
};

复杂度

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

[hot] 39. 数组中出现次数超过一半的数字

题目

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

题解

类似位运算处理,定义times来记录遍历过程中当前遍历元素在之前的遍历过程中出现的次数,初始值为1,定义result为最终结果,在遍历过程中其永远都等于出现次数最多的元素。遍历过程中如果result等于当前遍历元素则++times,否则–times,如果times变为0,则将当前遍历元素赋值给result,times重新赋值为1,遍历结束后返回result即可。示例代码如下:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int result = nums[0];
        int times = 1;
        for (int i = 1; i < nums.size(); ++i) {
            if (times == 0) {
                times = 1;
                result = nums[i];
            } else if (result == nums[i]) {
                ++times;
            } else {
                --times;
            }
        }

        return times > 0 ? result : -1;
    }
};

复杂度

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

56. 数组中数字出现的次数

题目

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

题解

将数组中所有的元素异或,即可得到只出现一次的两个元素a、b的异或值。可以找到a和b异或值二进制表达值的任意一位为1的位置,数组中这一位为1分为一组,为0分为一组,两组数据分别异或即可得到a和b。示例代码如下所示:

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int ret = 0;
        for (const auto& elem: nums) {
            ret ^= elem;
        }
        int div = 1; // 注意标志位的初始化
        while ((div & ret) == 0) { // & 能够找到只出现一次的两个数二进制表示的第一个不相同位
            div <<= 1;
        }
        int a = 0;
        int b = 0;
        for (const auto& elem: nums) {
            if (elem & div) { // & 能将a和b区分开来,原因同上
                a ^= elem;
            } else {
                b ^= elem;
            }
        }

        return vector<int>{a, b};
    }
};

复杂度

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值