目录

主要思路:
完全背包问题,每一个平方数可以选取多次。
本题的物品组合与顺序无关,对应于组合问题,因此先遍历物品,再遍历背包。
定义dp[j]表示背包容量为j时,装满背包所需完全平方数的最少数量。
- #include
- #include
-
- class Solution {
- public:
- int numSquares(int n) {
- std::vector<int>dp(n+1, n); // 求最少数量,因此初始化不应该为0
- dp[0] = 0;
- for(int i = 1; i*i <= n; i++){ // 遍历物品
- for(int j = i*i; j <= n; j++){ // 遍历背包
- dp[j] = std::min(dp[j], dp[j - i*i] + 1); // dp[j]表示不选取物品i*i
- }
- }
- return dp[n];
- }
- };
-
- int main(int argc, char* argv[]){
- // n = 12
- int test = 12;
- Solution S1;
- int res = S1.numSquares(test);
- std::cout << res << std::endl;
- return 0;
- }

主要思路:
本题只是要保持非零元素的相对顺序,没要求保持零元素的相对顺序(卡了很久,没看清楚题意);
用双指针算法即可,左指针和右指针初始化为0,左指针指向已处理元素序列的尾部(左指针左边全是非零值),右指针指向待处理元素序列的头部;当右指针遇到非零值,交换左右指针的值即可;
- #include
- #include
-
- class Solution {
- public:
- void moveZeroes(std::vector<int>& nums) {
- int left = 0, right = 0;
- while(right < nums.size()){
- if(nums[right] != 0){
- std::swap(nums[left], nums[right]);
- left++;
- }
- right++;
- }
- }
- };
-
- int main(int argc, char* argv[]){
- // nums = [0, 1, 0, 3, 12]
- Solution S1;
- std::vector<int> test = {0, 1, 0, 3, 12};
- S1.moveZeroes(test);
- for(int num : test) std::cout << num << " ";
- std::cout << std::endl;
- return 0;
- }

主要思路:
对于数组nums,定义cnt[i]表示nums数组中小于等于i的数的数量,假设重复的数是target,则对于[1, target-1]里所有的数满足cnt[i] <= i;对于[target, n]里的所有数满足cnt[i] > i;
不断二分,直到找到target即可。
- #include
- #include
-
- class Solution {
- public:
- int findDuplicate(std::vector<int>& nums) {
- int left = 1, right = nums.size() - 1;
- int ans;
- while(left <= right){
- int mid = left + (right - left) / 2;
- int count = 0;
- for(int num : nums){
- if(num <= mid) count++; // 统计小于mid的元素个数
- }
- if(count <= mid){ // 表面mid在目标元素左侧
- left = mid + 1;
- }
- else{ // mid可能是目标元素,也可能在目标元素右侧
- right = mid - 1;
- ans = mid; // 记录目标元素
- }
- }
- return ans;
- }
- };
-
- int main(int argc, char* argv[]){
- // nums = [1, 3, 4, 2, 2] 001 011 100 010 010
- std::vector<int> test = {1, 3, 4, 2, 2};
- Solution S1;
- int res = S1.findDuplicate(test);
- std::cout << res << std::endl;
- return 0;
- }

主要思路:
基于层次遍历序列化和反序列化;
- #include
- #include
- #include
- #include
-
- struct TreeNode {
- int val;
- TreeNode *left;
- TreeNode *right;
- TreeNode(int x) : val(x), left(NULL), right(NULL) {}
- };
-
- class Codec {
- public:
- std::string serialize(TreeNode* root) {
- // 层次遍历序列化
- std::queue
q; - std::string res;
- q.push(root);
- while(!q.empty()){
- TreeNode* cur = q.front();
- q.pop();
- if(cur != nullptr){
- res.append(std::to_string(cur->val) + ',');
- q.push(cur->left);
- q.push(cur->right);
- }
- else{
- res.append("#,"); // 使用"#"表示nullptr
- }
- }
- return res;
- }
-
- TreeNode* deserialize(std::string data){
- if(data[0] == '#') return nullptr;
- std::vector
vec; - std::string tmp = "";
- for(int i = 0; i < data.length(); i++){
- if(data[i] != ','){
- tmp += data[i];
- }
- else{
- vec.push_back(tmp);
- tmp = "";
- }
- }
- // 层次遍历反序列化
- std::queue
q; - TreeNode *root = new TreeNode(std::stoi(vec[0])); // i = 0 作为根节点
- q.push(root);
- int i = 1; // 从1开始
- while(!q.empty()){
- TreeNode* cur = q.front();
- q.pop();
- if(vec[i] != "#"){ // 重构左孩子
- TreeNode* left = new TreeNode(std::stoi(vec[i]));
- cur->left = left;
- q.push(left);
- }
- i++;
- if(vec[i] != "#"){ // 重构右孩子
- TreeNode* right = new TreeNode(std::stoi(vec[i]));
- cur->right = right;
- q.push(right);
- }
- i++;
- }
- return root;
- }
- };
-
- int main(int argc, char* argv[]){
- // root = [1, 2, 3, null, null, 4, 5]
- TreeNode *Node1 = new TreeNode(1);
- TreeNode *Node2 = new TreeNode(2);
- TreeNode *Node3 = new TreeNode(3);
- TreeNode *Node4 = new TreeNode(4);
- TreeNode *Node5 = new TreeNode(5);
-
- Node1->left = Node2;
- Node1->right = Node3;
- Node3->left = Node4;
- Node3->right = Node5;
-
- Codec S1;
- std::string test1 = S1.serialize(Node1);
- std::cout << test1 << std::endl;
- TreeNode* res = S1.deserialize(test1);
-
- std::queue
q; - q.push(res);
- while(!q.empty()){
- TreeNode* cur = q.front();
- q.pop();
- if(cur != nullptr){
- std::cout << cur->val << " ";
- q.push(cur->left);
- q.push(cur->right);
- }
- else{ // nullptr
- std::cout << "null" << " ";
- }
- }
- std::cout << std::endl;
- return 0;
- }

主要思路:
dp[i]表示以nums[i]结尾的子序列的最大长度;
状态转移方程:
dp[i] = std::max(dp[i], dp[j] + 1) if nums[i] > nums[j] && i > j;
- #include
- #include
-
- class Solution {
- public:
- int lengthOfLIS(std::vector<int>& nums) {
- int res = 1;
- std::vector<int> dp(nums.size(), 1); // dp[i]表示以第i个字符结尾的子序列的最长长度
- for(int i = 1; i < nums.size(); i++){
- for(int j = 0; j <= i; j++){
- if(nums[i] > nums[j]){
- dp[i] = std::max(dp[i], dp[j]+1);
- }
- }
- res = std::max(res, dp[i]);
- }
- return res;
- }
- };
-
- int main(int argc, char* argv[]){
- // nums = [10, 9, 2, 5, 3, 7, 101, 18]
- std::vector<int> test = {10, 9, 2, 5, 3, 7, 101, 18};
- Solution S1;
- int res = S1.lengthOfLIS(test);
- std::cout << res << std::endl;
- return 0;
- }

主要思路:
基于回溯法,递归删除每一个字符,判断最后的字符串是否合法。
- #include
- #include
- #include
-
- class Solution {
- public:
- std::vector
removeInvalidParentheses(std::string s) { - // 首先计算最少需要删除的左括号和右括号个数
- int lmove = 0, rmove = 0;
- for(int i = 0; i < s.length(); i++){
- if(s[i] == '(') lmove++;
- else if(s[i] == ')'){
- if(lmove > 0) lmove--;
- else rmove++;
- }
- }
- std::vector
res; // 返回结果 - dfs(s, 0, lmove, rmove, res);
- return res;
- }
-
- // 递归回溯
- void dfs(std::string &s, int start, int lmove, int rmove, std::vector
& res) { - if(lmove == 0 && rmove == 0){ // 收获结果
- if(isValid(s)){
- res.push_back(s);
- }
- return;
- }
-
- for(int i = start; i < s.length(); i++){
- if(i != start && s[i] == s[i-1]) continue; // 去重
- // 还需要删除的括号个数小于能删除的字符个数
- if(lmove + rmove > s.length() - i) return; // 提前剪枝
-
- // 删除当前字符
- if(s[i] == '(' && lmove > 0){ // 当前字符是左括号
- s.erase(i, 1);
- dfs(s, i, lmove-1, rmove, res); // 删除后,下一次开始还是第cur个字符
- s.insert(s.begin() + i, '('); // 回溯恢复
- }
- else if(s[i] == ')' && rmove > 0){ // 当前字符是右括号
- s.erase(i, 1);
- dfs(s, i, lmove, rmove-1, res);
- s.insert(s.begin() + i, ')'); // 回溯恢复
- }
- }
- }
-
- // 判断字符串是否有效
- bool isValid(std::string str){
- int left = 0;
- for(int i = 0; i < str.length(); i++){
- if(str[i] == '(') left++;
- else if(str[i] == ')') left--;
- if(left < 0){ // 当某次 left < 0 时,表明当前右括号的个数多于左括号,即 ()())( 的情况
- return false;
- }
- }
- if(left == 0) return true;
- else return false;
- }
- };
-
- int main(int argc, char* argv[]){
- // s = "()())()"
- std::string test = "()())()";
- Solution S1;
- std::vector
res = S1.removeInvalidParentheses(test); - for(std::string str : res) std::cout << str << " ";
- std::cout << std::endl;
-
- return 0;
- }

主要思路:
对于每一天,都有三种状态,处于买入状态,处于卖出状态,处于冷冻期状态。
定义dp[i][0]表示第 i 天处于买入状态,dp[i][1]处于卖出状态,dp[i][2]处于冷冻期状态。
初始化dp[0][0] = -prices[0],dp[0][1] = 0, dp[0][2] = 0;
状态转移方程:
dp[i][0] = max(dp[i-1][2] - prices[i], dp[i-1][0]); // 前一天可以是冷冻期,也可以是处于买入状态;
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i], dp[i-1][2]); // 前一天可以是卖出状态,也可以是买入状态和冷冻期状态。
dp[i][2] = dp[i-1][1]; // 前一天只能是卖出状态。
- #include
- #include
-
- class Solution {
- public:
- int maxProfit(std::vector<int>& prices) {
- // dp[i][0]买入、dp[i][1]卖出、dp[i][2]冷冻期三种状态
- std::vector
int>> dp(prices.size(), std::vector<int>(3, 0)); - // 初始化
- dp[0][0] = -prices[0];
- dp[0][1] = 0;
- dp[0][2] = 0;
- // 状态转移
- for(int i = 1; i < prices.size(); i++){
- dp[i][0] = std::max(dp[i-1][2] - prices[i], dp[i-1][0]); // dp[i-1][0]表示第i-1天就是买入的状态
- dp[i][1] = std::max(dp[i-1][1], std::max(dp[i-1][0] + prices[i], dp[i-1][2]));
- dp[i][2] = dp[i-1][1];
- }
- return std::max(dp[prices.size() - 1][1], dp[prices.size() - 1][2]);
- }
- };
-
- int main(int argc, char* argv[]){
- // prices = [1, 2, 3, 0, 2]
- std::vector<int> test = {1, 2, 3, 0, 2};
- Solution S1;
- int res = S1.maxProfit(test);
- std::cout << res << std::endl;
- return 0;
- }

主要思路:
基于动态规划,dp[i][j] 表示戳爆在区间 [i, j] 内所有气球能获得的最大硬币数;
状态转移方程:dp[i][j] = std::max(dp[i][j], dp[i][k-1] + dp[k+1][j] + a[i-1]*a[k]*a[j+1]);
k 表示在区间[i, j]中最后戳爆的气球。
- #include
- #include
- #include
-
- class Solution {
- public:
- int maxCoins(std::vector<int>& nums){
- int n = nums.size();
- std::vector<int> a(n+2); // 在两个边界填充1
- for(int i = 1; i <= n; i++){
- a[i] = nums[i - 1];
- }
- a[0] = a[n + 1] = 1;
-
- // dp[i][j] 表示戳爆在区间[i, j]内所有气球能获得的最大硬币数
- std::vector
int>> dp(nums.size() + 2, std::vector<int>(nums.size() + 2, 0)); - for(int i = 1; i <= n; i++){ // 初始化区间长度为1时的最大硬币数
- dp[i][i] = a[i-1] * a[i] * a[i+1];
- }
- for(int len = 2; len <= n; len++){ // 枚举区间长度
- for(int i = 1; i + len - 1 <= n; i++){ // 枚举起点
- int j = i + len - 1; // 区间终点
- for(int k = i; k <= j; k++){ // 枚举最后一个戳爆的气球
- dp[i][j] = std::max(dp[i][j], dp[i][k-1] + dp[k+1][j] + a[i-1]*a[k]*a[j+1]);
- }
- }
- }
- return dp[1][n];
- }
- };
-
- int main(int argc, char* argv[]){
- // nums = [3, 1, 5, 8]
- Solution S1;
- std::vector<int> test = {3, 1, 5, 8};
- int res = S1.maxCoins(test);
- std::cout << res << std::endl;
- return 0;
- }

主要思路:
经典完全背包问题;本题的结果与顺序无关,属于组合问题,因此先遍历物品,再遍历背包。
- #include
- #include
- #include
-
- class Solution {
- public:
- int coinChange(std::vector<int>& coins, int amount) {
- std::vector<int> dp(amount+1, INT_MAX);
- dp[0] = 0;
- for(int i = 0; i < coins.size(); i++){ // 遍历物品
- for(int j = coins[i] ; j <= amount; j++){ // 遍历背包
- if(dp[j - coins[i]] != INT_MAX){ // 确保dp[j-coins[i]]恰好可以由硬币构成
- dp[j] = std::min(dp[j], dp[j - coins[i]] + 1);
- }
- }
- }
- if(dp[amount] == INT_MAX) return -1;
- return dp[amount];
- }
- };
-
- int main(int argc, char* argv[]){
- // coins = [1, 2, 5], amount = 11
- std::vector<int> test = {1, 2, 5};
- int target = 11;
- Solution S1;
- int res = S1.coinChange(test, target);
- std::cout << res << std::endl;
- return 0;
- }