目录
一、数组
1365. 有多少小于当前数字的数字
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> numsSort = nums;
sort(numsSort.begin(), numsSort.end());
unordered_map<int, int> record;
// 从后向前赋值,保证下标为第一个出现的元素
for(int i = numsSort.size() - 1; i >= 0; i--) {
record[numsSort[i]] = i;
}
vector<int> res(nums.size());
for (int i = 0; i < nums.size(); i++) {
res[i] = record[nums[i]];
}
return res;
}
941. 有效的山脉数组
bool validMountainArray(vector<int>& arr) {
// 双指针法
if (arr.size() <= 2) return false;
int left = 0;
int right = arr.size() - 1;
while (left < arr.size() - 1 && arr[left + 1] > arr[left]) {
left++;
}
while (right > 0 && arr[right - 1] > arr[right]) {
right--;
}
if (left == right && left != 0 && right != arr.size() - 1) return true;
else return false;
}
1207. 独一无二的出现次数
bool uniqueOccurrences(vector<int>& arr) {
unordered_map<int, int> arrCount;
for (int i : arr) {
arrCount[i]++;
}
set<int> count;
for (auto it = arrCount.begin(); it != arrCount.end(); it++) {
count.insert(it->second);
}
if (arrCount.size() == count.size()) return true;
else return false;
}
189. 轮转数组
void rotate(vector<int>& nums, int k) {
// 参考翻转字符串问题
k = k % nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
724. 寻找数组的中心下标
int pivotIndex(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
int tempSum = 0;
for (int i = 0; i < nums.size(); i++) {
if (sum - nums[i] == 2 * tempSum) return i;
tempSum += nums[i];
}
return -1;
}
922. 按奇偶排序数组 II
vector<int> sortArrayByParityII(vector<int>& nums) {
// 双指针法
int odd = 1;
for (int even = 0; even < nums.size(); even += 2) {
// 偶数位不符合,在奇数位寻找不符合的元素进行替换
if (nums[even] % 2 != 0) {
while (nums[odd] % 2 != 0) odd += 2;
swap(nums[odd], nums[even]);
}
}
return nums;
}
二、哈希表
205. 同构字符串
bool isIsomorphic(string s, string t) {
// 利用unordered_map直接记录字符与字符之间的映射
unordered_map<char, char> map1;
unordered_map<char, char> map2;
for (int i = 0, j = 0; i < s.size(); i++, j++) {
if (map1.find(s[i]) == map1.end()) { // map1保存s[i] 到 t[j]的映射
map1[s[i]] = t[j];
}
if (map2.find(t[j]) == map2.end()) { // map2保存t[j] 到 s[i]的映射
map2[t[j]] = s[i];
}
// 发现映射 对应不上,立刻返回false
if (map1[s[i]] != t[j] || map2[t[j]] != s[i]) {
return false;
}
}
return true;
}
925. 长按键入
bool isLongPressedName(string name, string typed) {
int index = 0;
// 记录重复元素数
int count = 0;
for (int i = 0; i < name.size(); i++) {
// 名字中有连续重复字符
if (i > 0 && name[i] == name[i - 1]) {
count--;
if (count < 0) return false;
}
// 名字中没有连续重复字符
else {
count = 0;
if (name[i] != typed[index]) return false;
if (index >= typed.size()) return false;
while (index + 1 < typed.size() && typed[index + 1] == typed[index]) {
index++;
count++;
}
index++;
}
}
// 是否有多余字符
if (index != typed.size()) return false;
return true;
}
三、二叉树
129. 求根节点到叶节点数字之和
int res;
vector<int> path;
int sumNumbers(TreeNode* root) {
res = 0;
path.clear();
if (!root) return res;
path.push_back(root->val);
BackTracking(root);
return res;
}
void BackTracking(TreeNode* node) {
// 返回条件
if (!node->left && !node->right) {
res += VecToInt(path);
return;
}
// 前序遍历
if (node->left) {
path.push_back(node->left->val);
BackTracking(node->left);
path.pop_back();
}
if (node->right) {
path.push_back(node->right->val);
BackTracking(node->right);
path.pop_back();
}
return;
}
int VecToInt(vector<int>& v) {
int res = 0;
for (int i : v) {
res = res * 10 + i;
}
return res;
}
1382. 将二叉搜索树变平衡
vector<int> elem;
TreeNode* balanceBST(TreeNode* root) {
// 搜索树元素存入数组
elem.clear();
Traversal(root);
BuildTree(root, 0, elem.size() - 1);
return root;
}
void Traversal(TreeNode* node) {
if (!node) return;
Traversal(node->left);
elem.push_back(node->val);
Traversal(node->right);
return;
}
void BuildTree(TreeNode* node, int start, int end) {
// 前序遍历
int mid = start + (end - start) / 2;
node->val = elem[mid];
if (start <= mid - 1) {
TreeNode* left = new TreeNode();
node->left = left;
BuildTree(left, start, mid - 1);
}
if (end >= mid + 1) {
TreeNode* right = new TreeNode();
node->right = right;
BuildTree(right, mid + 1, end);
}
return;
}
四、回溯
52. N皇后 II
int res;
int totalNQueens(int n) {
res = 0;
vector<string> chessboard(n, string(n, '.'));
BackTracking(0, chessboard, n);
return res;
}
void BackTracking(int row, vector<string>& chessboard, const int n) {
if (row == n) {
res++;
return;
}
for (int col = 0; col < n; col++) {
// 棋盘有效时才继续递归,无效则进行下一次同层迭代
if (IsValid(row, col, chessboard, n)) {
chessboard[row][col] = 'Q';
BackTracking(row + 1, chessboard, n);
chessboard[row][col] = '.';
}
}
}
// 检查当前棋盘是否合法
bool IsValid(int row, int col, vector<string>& chessboard, const int n) {
// 检查列
for (int i = 0; i < row; i++) {
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查135度角是否有皇后
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查45度角是否有皇后
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
五、贪心
649. Dota2 参议院
string predictPartyVictory(string senate) {
// 局部最优:有一次权利机会,就消灭自己后面的对手
// 全局最优:为自己的阵营赢取最大利益
// 利用队列储存两个阵营成员下标,出队代表被消灭
queue<int> teamR, teamD;
for (int i = 0; i < senate.size(); i++) {
if (senate[i] == 'R') teamR.push(i);
else teamD.push(i);
}
if (teamR.empty()) return "Dire";
if (teamD.empty()) return "Radiant";
// 模拟投票过程
while(!teamR.empty() && !teamD.empty()) {
// 优先剥夺身后下一个位置的对方阵营权利
if (teamR.front() < teamD.front()) {
int R = teamR.front();
teamD.pop();
teamR.pop();
// 该成员进入下一轮投票
teamR.push(R + senate.size());
}
else {
int D = teamD.front();
teamR.pop();
teamD.pop();
// 该成员进入下一轮投票
teamD.push(D + senate.size());
}
}
return teamR.empty() ? "Dire" : "Radiant";
}
要注意巧妙运用各种数据结构,尤其是流程模拟类问题。
1221. 分割平衡字符串
int balancedStringSplit(string s) {
// 贪心:每遇到一个平衡子串就收录
int res = 0;
stack<char> st;
for (char str : s) {
if (st.empty() || str == st.top()) st.push(str);
else {
st.pop();
if (st.empty()) res++;
}
}
return res;
}
本题可以不利用堆栈,利用一个count记录即可。
870. 优势洗牌
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
vector<int> res(nums1.size());
sort(nums1.begin(), nums1.end(), greater<int>());
// 从大到小排序,同时还记录了原下标位置
multimap<int, int, greater<int>> nums2_index;
for (size_t i = 0; i < nums2.size(); i++) {
nums2_index.insert(make_pair(nums2[i], i));
}
int big = 0;
int small = nums1.size() - 1;
// 田忌赛马策略:如果当前马比得过对方,就派出当前马;若比不过,派出最小马保存实力。
for (auto it = nums2_index.begin(); it != nums2_index.end(); it++) {
if (nums1[big] > it->first) {
res[it->second] = nums1[big];
big++;
}
else {
res[it->second] = nums1[small];
small--;
}
}
return res;
}
六、动态规划
132. 分割回文串 II[*]
int minCut(string s) {
if (s.size() <= 1) return 0;
// dp[i]:下标为0-i区间内字符串分割为回文子串的最小分割数
vector<int> dp(s.size());
// 初始化:由于要取最小值,应将数组初始化为可能的最大值,即字符串长度
for (int i = 0; i < s.size(); i++) dp[i] = i;
// 递推公式
for (int i = 1; i < s.size(); i++) {
// 0-i是回文则为0
if (isPalindromic(s, 0, i)) {
dp[i] = 0;
continue;
}
// 0-i非回文,j从0至i-1遍历,若j+1至i为回文,只需再分割一次:dp[i] = dp[j] + 1
// i一定会被更新,取最小值即可
for (int j = 0; j < i; j++) {
if (isPalindromic(s, j + 1, i)) {
dp[i] = min(dp[j] + 1, dp[i]);
}
}
}
return dp[s.size() - 1];
}
bool isPalindromic(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) return false;
}
return true;
}
131. 分割回文串问题使用了回溯法,本题采用回溯法会超时。一般需要返回所有结果的采用回溯法,而只需要记录数量的问题可以采用动态规划法。
动态规划递推公式中存在min/max时,要注意初始化时将元素置为可能的最大值或最小值,以免影响结果。本题递推公式较难理解,dp[i]不止取决于上一个元素,而取决于从0至i-1所有的dp数组元素,因此需要j从0至i-1进行循环,找到最小值即可。
为提高速度,一种优化方法是仍利用动态规划法,采用二维dp[i][j]数组记录下标为i至j的字符串是否为回文串,保存这些信息在循环中使用。
673. 最长递增子序列的个数[*]
int findNumberOfLIS(vector<int>& nums) {
if (nums.size() == 1) return 1;
int maxLength = 1;
int res = 0;
// length[i]:0-i的序列中最长递增子序列的长度【更新方式说明见题目300】
// cnt[i]:以nums[i]结尾的最长递增子序列【子序列含nums[i]】的个数
vector<int> length(nums.size(), 1);
vector<int> cnt(nums.size(), 1);
// 递推公式:
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
// 出现了更大元素,需要进行遍历判断该元素的出现是否改变了最大递增子序列长度
if (nums[i] > nums[j]) {
// 不影响最大递增子序列长度
if (length[j] + 1 > length[i]) {
length[i] = length[j] + 1;
cnt[i] = cnt[j];
}
// 最大子序列长度改变
else if (length[j] + 1 == length[i]) {
cnt[i] += cnt[j];
}
}
}
maxLength = max(length[i], maxLength);
}
for (int i = 0; i < nums.size(); i++) {
if (length[i] == maxLength) res += cnt[i];
}
return res;
}
【本题仍需深入思考】,其技巧在于定义了两个dp数组。
七、位运算
1356. 根据数字二进制下 1 的数目排序
class Solution {
private:
static int BitCount(int num) {
int count = 0;
while (num > 0) {
count += num & 1;
num >>= 1;
}
return count;
}
static bool cmp(int a, int b) {
int bitA = BitCount(a);
int bitB = BitCount(b);
if (bitA == bitB) return a < b;
else return bitA < bitB;
}
public:
vector<int> sortByBits(vector<int>& arr) {
sort(arr.begin(), arr.end(), cmp);
return arr;
}
};
按位进行“与”运算即可,要注意sort函数的使用。