数组
十进制转K进制数
给你一个整数 n(10 进制)和一个基数 k ,请你将 n 从 10 进制表示转换为 k 进制表示
int sumBase(int n, int k) {
int s=0;
int i=0;
while(n){
s+=(n%k)*pow(10,i++);
n/=k;
}
return s;
}
最小的k个数
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> vec(k, 0);
sort(arr.begin(), arr.end());
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}
};
时间复杂度:O(nlogn),其中 n 是数组 arr 的长度。算法的时间复杂度即排序的时间复杂度。
空间复杂度:O(logn),排序所需额外的空间复杂度为 O(logn)。
两数之和
找出数组中两个加起来等于目标的数
暴力
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0;i<nums.size();i++){
for(int j=0;j<nums.size();j++){
if(nums[i]+nums[j]==target&&i!=j){
return{i,j};
}
}
}
return{0,0};*/
}
哈希表
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> hashtable;
for(int i=0;i<nums.size();i++){
auto it =hashtable.find(target-nums[i]);
if(it!=hashtable.end()){
return {it->second,i};
}
hashtable[nums[i]]=i;
}
return {};
}
有序数组合并
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i=0,j=0,k=0;
int sorted[m+n];
while(i<m&&j<n){
if(nums1[i]<=nums2[j])
sorted[k++]=nums1[i++];
else
sorted[k++]=nums2[j++];
}
while(i<m){
sorted[k++]=nums1[i++];
}
while(j<n){
sorted[k++]=nums2[j++];
}
}
三个数最大乘积
int maximumProduct(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
return max(nums[0] * nums[1] * nums[n - 1], nums[n - 3] * nums[n - 2] * nums[n - 1]);
//返回最大的情况,最大三个正数相乘,或者前最小两个负数相乘再乘最大正数
}
数组中的逆序对
class Solution {
public:
int a[50000];
int merge(vector<int>&nums,int low,int high){
if(low>=high)return 0;
int mid=(low+high)/2;
int i=low,j=mid+1,k=0;
int res=merge(nums,i,mid)+merge(nums,j,high);//递归划分左右子序列
while(i<=mid&&j<=high){
if(nums[i]<=nums[j]){
a[k++]=nums[i++];
}
else{
a[k++]=nums[j++];
res+=mid-i+1;
//当 nums[i] > nums[j]的时候统计:
// [i, ..., mid]的元素均大于 [j] 位置上的元素,
//此时产生的逆序对个数为 mid-i+1
}
}
while (i <= mid) a[k++] = nums[i++];
while (j <= high) a[k++] = nums[j++];
for(i=low,j=0;i<=high;i++,j++){
nums[i]=a[j];
}
return res;
}
int reversePairs(vector<int>& nums) {
return merge(nums,0,nums.size()-1);
}
};
这里mid-i+1是当前大于序列R[j]的逆序对的的长度
例如L = [8, 12, 16, 22, 100] R = [9, 26, 55, 64, 91]
8<9放入i++
12>9,那么12~100都>9,贡献的逆序对数为mid(100)-i(12)+1;j++
王道风格版本
class Solution {
int cnt = 0;
int[] temp;
public int reversePairs(int[] nums) {
int n = nums.length;
temp = new int[n];
mergeSort(nums, 0, n - 1);
return cnt;
}
private void mergeSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
// 像不像二叉树的后序遍历?
int mid = (right - left) / 2 + left;
// 递 => 将数组分割成不可再分的小块
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 归 => 逐步合并已完成排序的左右小块
merge(arr, left, mid, right);
}
private void merge(int[] arr, int left, int mid, int right) {
// 复制一下,准备归并
for (int i = left; i <= right; i++) {
temp[i] = arr[i];
}
// 修改原数组,归并 temp
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (temp[i] > temp[j]) {
arr[k] = temp[j++];
// mid 前有多少个 temp[j] 比当前 temp[i] 小
cnt += mid - i + 1;
} else {
arr[k] = temp[i++];
}
k++;
}
while (i <= mid) {
arr[k++] = temp[i++];
}
while (j <= right) {
arr[k++] = temp[j++];
}
}
}
旋转数组的最小数字
暴力
class Solution {
public:
int minArray(vector<int>& numbers) {
vector<int>::iterator it;
for(it=numbers.begin()+1;it!=numbers.end();it++)
if(*it<*(it-1))
return *it;
return numbers[0];
}
};
时间复杂度O(n)
调整数组顺序使奇数位于偶数前面
vector<int> exchange(vector<int>& nums)
{
int i = 0, j = nums.size() - 1;
while (i < j)
{
while(i < j && (nums[i] & 1) == 1) i++;
while(i < j && (nums[j] & 1) == 0) j--;
swap(nums[i], nums[j]);
}
return nums;
}
原地转置矩阵
class Solution {
public:
vector<vector<int>> transpose(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> transposed(n, vector<int>(m));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
transposed[j][i] = matrix[i][j];
}
}
return transposed;
}
};
滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(!nums.size()) return vector<int> {};
int n = nums.size();
priority_queue<pair<int, int>> q; // 优先队列,构建当前元素及其在数组中坐标的对应关系
for (int i = 0; i < k; ++i) { // 第一个滑动窗口的值和位置的对应关系
q.emplace(nums[i], i);
}
vector<int> ans = {q.top().first}; // 初始化将第一个滑动窗口中元素最大的值存到vector中
for (int i = k; i < n; ++i) { // 滑动窗口右移
q.emplace(nums[i], i);
while (q.top().second <= i - k) { // 这个值在数组nums中的位置出现在滑动窗口左边界的左侧
q.pop(); // 永久移除该值,直到找到一个在滑动窗口中的最大值
}
ans.push_back(q.top().first); // 将该最大值压入vector中
}
return ans;
}
二分查找
旋转数组的最小数字
class Solution {
public:
int minArray(vector<int>& numbers) {
int low = 0;
int high = numbers.size() - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (numbers[mid] < numbers[high]) {//右边一定有序,往左找
high = mid;//包括mid
}
else if (numbers[mid] > numbers[high]) {//最小值一定在mid右边
low = mid + 1;//mid大一些,肯定不是最小值
}
else {
high -= 1;//重复元素不二分
}
}
return numbers[low];
}
};
时间复杂度O(log2n)
若元素完全相同,为O(n)
空间复杂度O(1)
在排序数组中查找数字
统计一个数字在排序数组中出现的次数。
二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size()== 0) return 0;
return BinarySearch(nums,target+1)-BinarySearch(nums,target);
//比如5,7,7,8,8,10找8,用10的下标减去第一个8的下标
//就算数组里不包含target+1也没关系,指针会停在大于target的第一个数上
}
int BinarySearch(vector<int>a,int target){
int low=0,high=a.size()-1;
int mid;
while(low<=high){
mid=(low+high)/2;
if(a[mid]<target)low=mid+1;//target值大于当前mid值,应该找往右找
else{//a[mid]大于等于target
high=mid-1;//左移,保证是大于等于target的第一个
}
}
return high;
}
};
字符串
统计字符串中数字个数
以字符串"Hello world122334455!"
为例
哈希表实现
void CountNum(string s)
{
map<char,int> hashtable;
for(int i=0; i<s.length(); i++)
{
if(s[i]>='0'&&s[i]<='9')
{
hashtable[s[i]]++;
}
}
for(int i='0';i<'9';i++){
cout<<"数字 "<<i-'0'<<" 的个数: "<< hashtable[i]<<endl;
}
}
回文串
判断字符串是否是回文串
递归版本
bool huiwencuan(char a[],int i,int j)
{
if(i==j)
return true;
else if(i<j)
{
if(a[i]==a[j])
{
i++;j++;//判断下一位
huiwencuan(a,i,j);
}
else
return false;
}
}
非递归版本
bool huiwencuan2(string a)
{
int n=a.length();
for(int i=0,j=n-1; i<j; i++,j--)
{
if(i<j)
{
if(a[i]!=a[j])
return false;
}
}
return true;
}
字符串旋转
给定两个字符串, A 和 B。
A 的旋转操作就是将 A 最左边的字符移动到最右边。 例如, 若 A = ‘abcde’,在移动一次之后结果就是’bcdea’ 。如果在若干次旋转操作之后,A 能变成B,那么返回True。
bool rotateString(string s, string goal) {
if(s.length()!=goal.length())return false;//长度不等肯定不等
s+=s;
if(s.find(goal)!=-1)return true;//无法匹配
return false;
}
时间复杂度O(1)
最长重复子串
一个重复字符串是由两个相同的字符串首尾拼接而成,例如abcabc便是长度为6的一个重复字符串,而abcba则不存在重复字符串。
给定任意字符串,请找出其中的最长重复子串。
string longestDupSubstring(string a) {
// write code here
int n=a.length();
int x=0;
string s="";
for(int i=n/2;i>0;--i){//从大到小枚举长度,最大长度只可能为总长度的一半
for(int j=0;j<n;j++){//枚举起始地址
if(a[j]==a[j+i]){
++x;//最大长度加一
s+=a[j];
}else{
x=0;//这个长度的重复子串不存在,归零
s="";
}
if(x==i){
return s;
}
}
}
return s;
}
最长无重复子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
int lengthOfLongestSubstring(string s) {
// 哈希集合,记录每个字符是否出现过
multiset<char> occ;
int n = s.size();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
// 枚举左指针的位置,初始值隐性地表示为 -1
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.erase(s[i - 1]);
}
while (rk + 1 < n && !occ.count(s[rk + 1])) {
// 不断地移动右指针
occ.insert(s[rk + 1]);
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1);
}
return ans;
}
写一个函数,把字符串中所有的空格替换为%20
class Solution {
public:
string replaceSpace(string s) {
for(int i=0;i<s.length();i++){
if(s[i]==' '){
s[i]='%';
s.insert(i+1,"20");
}
}
//return s.replace(" ","%20");
return s;
}
};
单词顺序调换
一个英文句子中所有的单词顺序调换。例如,“Cricket is very strong.” 转化为 “strong very is Cricket”
string reverseWords(string s) {
string res="",temp;
istringstream is(s);
while(is>>temp){
res=temp+" "+res;
}
res.pop_back();
return res;
}
字符串转整数
int strToInt(string str) {
long res=0; bool negative=false;
int i=0;
while(str[i]==' ')//去除开头空格
i++;
if(!isdigit(str[i])&&str[i]!='+'&&str[i]!='-')//开头若不为数字或正负号返回0
return 0;
if(str[i]=='-'){negative=true; i++;}//判断正负
else if(str[i]=='+') i++;
while(isdigit(str[i]))//累加,连着+-也不管,最终返回0
{
res=res*10+str[i]-'0';
if(negative==false&&res>INT_MAX)//判断是否越界
return INT_MAX;
else if(negative==true&&-res<INT_MIN)
return INT_MIN;
i++;
}
return negative?-res:res;
}
DFS
岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
int s=0;
int m,n;
int numIslands(vector<vector<char>>& grid) {
m=grid.size();
n=grid[0].size();
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(grid[i][j]=='1'){//找到陆地
DFS(grid,i,j);
s++;//岛屿加1
}
cout<<m<<n;
return s;
}
void DFS(vector<vector<char>>& grid,int i,int j){
//上下左右 找陆地
grid[i][j] = '0';//标记探寻过
if (i-1>=0&&grid[i-1][j]=='1')DFS(grid,i-1,j);
//如果下一处是陆地,继续遍历以标记
if (i+1<m&&grid[i+1][j]=='1')DFS(grid,i+1,j);
if (j-1>=0&&grid[i][j-1]=='1')DFS(grid,i,j-1);
if (j+1<n&&grid[i][j+1]=='1')DFS(grid,i,j+1);
}
};
动态规划
求组合数
int computeCMN(int m, int n) {
if (n == 0 || m == n) return 1;
int c1 = computeCMN(m - 1, n);
int c2 = computeCMN(m - 1, n - 1);
return c1 + c2;
}
组合问题
①设m.n均为正整数,n可表示为一些不超过m的正整数之和(n<m),f(n,m)为这种组合方式的数目。
比如f(4,3)=4,有4种表示方式:3+1,2+2,2+1+1,1+1+1+1.使用递归方式计算完成函数int f(n,m)
https://blog.csdn.net/sinat_42483341/article/details/108712695?utm_source=app&app_version=4.15.0&code=app_1562916241&uLinkId=usr1mkqgl919blen
②m个相同的小球,放入n个相同的箱子,允许有的箱子空,求共有多少种分配的方法。
比如5个小球放入5个盒子
1)5个小球都放在一个盒子里,剩下4个盒子都是空的;
5 0 0 0 0
2)4个小球放在一个盒子里,1个小球放在1个盒子里,剩下3个盒子是空的;
4 1 0 0 0
3)3个小球放在一个盒子里,2个小球放在1个盒子里,剩下3个盒子是空的;
3 2 0 0 0
4)3个小球放在一个盒子里,1个小球放在1个盒子里,1个小球放在1个盒子里,剩下2个盒子是空的;
3 1 1 0 0
5)2个小球放在一个盒子里,2个小球放在1个盒子里,1个小球放在1个盒子里,剩下2个盒子是空的;
2 2 1 0 0
6)2个小球放在一个盒子里,1个小球放在1个盒子里,1个小球放在1个盒子里,1个小球放在1个盒子里,剩下2个盒子是空的;
2 1 1 1 0
7)5个盒子每个盒子放一个小球;
1 1 1 1 1
5个球放5个盒子,可以拆分为,5个球放4个盒子和5个球放满5个盒子
5个球放4个盒子,可以拆分为,5个球放3个盒子和5个球放满4个盒子
5个球放3个盒子,可以拆分为,5个球放2个盒子和5个球放满3个盒子
5个球放2个盒子,可以拆分为,5个球放1个盒子和5个球放满2个盒子
5个球放1个盒子,只有一种方法
③苹果放盘子
分为两种状况,n为苹果数;m为盘子数
第一种情况:当苹果的数量小于盘子的数量时,即 n<m时,问题就是转化为将 n 个苹果放到 n 个盘子中;
第二种情况:当苹果数大于盘子数量时,即 n>=m 时,要分析:
(1)将至少其中一个盘子不放,那就是m 个苹果放到 (m-1) 个盘子中的方法
(2)每个盘子放一个,然后就是(n-m)个放在 m 个盘子的方法
————————————————
本情况原文链接:https://blog.csdn.net/seagal890/article/details/89978767
int f(int n,int m){
if(n==0||m==1)return 1;
if(m>n)return f(n,n);
return f(n,m-1)+f(n-m,m);
}
青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
class Solution {
public:
#define MAX_NUM 1000000007
int numWays(int n) {
if(n <= 1) return 1;
int dp[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n+1; i++){
dp[i] = (dp[i-1] + dp[i-2])%MAX_NUM;
}
return dp[n];
}
};
01背包
二维
dp[i][j]= max(dp[i-1][j],dp[[i-1][j-w]+v)
public int knapsack(int W, int N, int[] weights, int[] values) {
int[][] dp = new int[N + 1][W + 1];
for (int i = 1; i <= N; i++) {
int w = weights[i - 1], v = values[i - 1];
for (int j = 1; j <= W; j++) {
if (j >= w) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
}
一维
public int knapsack(int W, int N, int[] weights, int[] values) {
int[] dp = new int[W + 1];
for (int i = 1; i <= N; i++) {
int w = weights[i - 1], v = values[i - 1];
for (int j = W; j >= 1; j--) {
if (j >= w) {
dp[j] = Math.max(dp[j], dp[j - w] + v);
}
}
}
return dp[W];
}
零钱兑换
int coinChange(vector<int>& coins, int amount) {
int max = amount+1;
vector<vector<int>> dp(coins.size()+1,vector<int>(amount+1));
for(int i=1;i<amount+1;i++)
dp[0][i]=max;//max就是换不了
for(int i=1;i<coins.size()+1;i++){
int coin = coins[i-1];
for(int j=1;j<amount+1;j++){
if (coin > j) {//如果硬币值大于amount,肯定无法兑换
dp[i][j] = dp[i-1][j];
}
else {
dp[i][j] = min(dp[i-1][j], dp[i][j-coin] + 1);
//比较加这个硬币或者不加这个硬币哪个开销小
}
//cout<<dp[i][j]<<" ";
}
//cout<<endl;
}
//最后一个元素是,最少开销
return dp[coins.size()][amount]==max?-1:dp[coins.size()][amount];
//能换就返回最后一个,否则返回-1
}
零钱兑换Ⅱ
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
amount = 5, coins = [1, 2, 5]
\ 0 1 2 3 4 5
0 1 0 0 0 0 0
1 1 1 1 1 1 1
2 1 1 2 2 3 3
5 1 1 2 2 3 4
int change(int amount, vector<int>& coins) {
vector<vector<int>> dp(coins.size()+1,vector<int>(amount+1));
dp[0][0]=1;
for(int i=1;i<coins.size()+1;i++)
for(int j=0;j<amount+1;j++){
if(j<coins[i-1])dp[i][j]=dp[i-1][j];
else{
dp[i][j]=dp[i][j-coins[i-1]]+dp[i-1][j];
}
}
return dp[coins.size()][amount];
}
组合总数
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1);
//dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+dp[i=nums[2]]+...
//举个例子比如nums=[1,3,4],target=7;
//dp[7]=dp[6]+dp[4]+dp[3]
//其实就是说7的组合数可以由三部分组成,1和dp[6],3和dp[4],4和dp[3];
//是为了算上自己的情况,比如dp[1]可以由dp【0】和1这个数的这种情况组成。
dp[0]=1;
for(int i=1;i<=target;i++)
{
for(int num:nums)
{
if(i>=num&& dp[i - num] < INT_MAX - dp[i])
{
dp[i]+=dp[i-num];
}
}
cout<<dp[i];
}
return dp[target];
}
//dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+dp[i=nums[2]]+…
//举个例子比如nums=[1,3,4],target=7;
//dp[7]=dp[6]+dp[4]+dp[3]
//其实就是说7的组合数可以由三部分组成,1和dp[6],3和dp[4],4和dp[3];
小结:
组合问题公式
dp[i] += dp[i-num]
True、False问题公式
dp[i] = dp[i] or dp[i-num]
最大最小问题公式
dp[i] = min(dp[i], dp[i-num]+1)或者dp[i] = max(dp[i], dp[i-num]+1)
最长公共子序列
int longestCommonSubsequence(string text1, string text2) {
int m=text1.length();
int n=text2.length();
vector<vector<int>> dp(m+1,vector<int>(n+1));
//0行0列代表空字符
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++){
if(text1[i-1]==text2[j-1])//字符相等,公共子序列可以加1
dp[i][j]=1+dp[i-1][j-1];
else//字符不相等,选择最长的一个公共子序列长度
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
return dp[m][n];
}
杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
vector<int> row;
while(numRows--){
int i=row.size()-1;
row.push_back(1);
for(;i>0;i--){
row[i]=row[i-1]+row[i];
}
res.push_back(row);
}
return res;
}
杨辉三角Ⅱ
返回「杨辉三角」的第 rowIndex 行。
vector<int> getRow(int rowIndex) {
rowIndex++;
vector<int> row;
while(rowIndex--){
int i=row.size()-1;
row.push_back(1);
for(;i>0;i--){
row[i]=row[i-1]+row[i];
}
}
return row;
}
最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
string longestPalindrome(string s) {
int n=s.length();
if(n<2)return s;
// dp[i][j] 表示 s[i..j] 是否是回文串
vector<vector<bool>> dp(n,vector<bool>(n));
// 初始化:所有长度为 1 的子串都是回文串
for(int i=0;i<n;i++){
dp[i][i]=true;
}
int maxLen=1;
int begin=0;
// 先枚举子串长度
for (int L = 2; L <= n; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for(int i=0;i<n;i++){
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j=L+i-1;
// 如果右边界越界,就可以退出当前循环
if(j>=n)break;
if(s[i]!=s[j])dp[i][j]=false;
else{
if(j-i<3){//如果是中间3个,那么外面两个相同一定是回文
//如果是中间两个那就中间两个是回文
dp[i][j]=true;
}else{//基于之前对回文的判断
dp[i][j]=dp[i+1][j-1];
}
}
if(dp[i][j]&&L>maxLen){
maxLen=L;
begin=i;
}
}
}
return s.substr(begin,maxLen);
}
连续子数组的最大和
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size()+1,-100);
if(nums.size()==0)return 0;//空数组返回0
dp[0]=nums[0];//只有一个数字只能是自身
for(int i=1;i<nums.size();i++){
dp[i]=max(dp[i-1]+nums[i],nums[i]);
//要么自己最大,从自己开始算起一个新的序列
//要么前面的加上自己变得更大
}
return *max_element(dp.begin(),dp.end());
}
最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。
例如,nums = [10,9,2,5,3,7,101,18] 最长递增子序列是 [2,3,7,101],因此长度为 4 。
//主要思想
//进行n轮循环,其中第i轮循环过程为
//从第一个元素到元素i-1,找到加上元素i能够成递增序列的所有元素,并生成新的递增序列,找到最长的那条序列,就是元素i所能构成的递增序列的最大长度.
//dp[i]=max{dp[i],dp[j]+1} j=0~i-1
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==0)return 0;//空表
vector<int> dp(n,1);
for(int i=1;i<n;i++)//从第二个元素开始找,第一个元素构成的增序长一定是1
for(int j=0;j<i;j++){//选取0到i-1的元素加上元素i所构成的最大增序
if(nums[j]<nums[i]){//构成增序
dp[i]=max(dp[i],dp[j]+1);
}
}
return *max_element(dp.begin(),dp.end());
}
乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
int maxProduct(vector<int>& nums) {
vector<int> m(nums.size()+1);//记录最小的数
vector<int> n(nums.size()+1,-100);//记录最大的数
if(nums.size()==0)return 0;//空数组返回0
m[0]=nums[0];n[0]=nums[0];
for(int i=1;i<nums.size();i++){
//要么前面的乘积×自己最大,否则连续乘积会断开,从自己开始重算
n[i]=max(nums[i],max(n[i-1]*nums[i],m[i-1]*nums[i]));
//要么自己本身最大,从自己开始算一个新的子列
//要么之前的序列乘上自己后更大(即自己是正数,之前是正数)
//要么自己是个负数,之前最小的序列乘上自己后变得最大(即自己是负数,之前是负数)
//m就是前面那个最小的子列乘积
m[i]=min(nums[i],min(m[i-1]*nums[i],n[i-1]*nums[i]));
//要么自己本身最小
//要么之前的序列乘上自己后是最小(即自己是正数,之前是负数)
//要么自己是个负数,之前最大的序列乘上自己后变得最小(即自己是负数,之前是正数)
}
return *max_element(n.begin(),n.end());
}
爬楼梯
int climbStairs(int n) {
int d[100];
d[0]=1;
d[1]=1;
for(int i=2;i<=n;i++)
d[i]=d[i-1]+d[i-2];
return d[n];
}
最小代价爬楼梯
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size()+1);
dp[0]=0;dp[1]=0;
for(int i=2;i<=cost.size();i++)
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
return dp[cost.size()];
}
繁殖问题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n, f[N]; // f[i]表示第i天机器人的个数
int main()
{
cin >> n;
// 前三天只有第一天的成熟机器人在繁殖
for (int i = 0; i <= 3; i ++)
f[i] = i;
// 第i天机器人的个数 = 前一天所有机器人的个数 + 三天前所有机器人的个数(即成熟机器人的个数)
// 即f[i] = f[i -1] + f[i - 3];
for (int i = 4; i <= 100; i ++)
f[i] = f[i -1] + f[i - 3];
cout << f[n] << endl;
return 0;
}
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
int rob(vector<int>& nums) {
int n=nums.size();
if(n==0)return 0;
if(n==1)return nums[0];
vector<int> v=vector<int>(n,0);
v[0]=nums[0];
v[1]=max(nums[0],nums[1]);
for(int i=2;i<n;i++){
v[i]=max(v[i-2]+nums[i],v[i-1]);
}
return v[n-1];
}
打家劫舍Ⅱ
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
实质就是在上一题基础上首尾相连了,所以拆分成两个序列,一个从0~n-1,一个从1-n
0~n-1
1~n
int rob(vector<int>& nums) {
int n=nums.size();
if(nums.size()==0)return 0;
if(nums.size()==1)return nums[0];
vector<int> a(nums.size(),0);
a[0]=nums[0];
a[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size()-1;i++){
a[i]=max(a[i-2]+nums[i],a[i-1]);
}
vector<int> b(nums.size(),0);
nums.erase(nums.begin());
b[0]=nums[0];
b[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++){
b[i]=max(b[i-2]+nums[i],b[i-1]);
}
cout<<a[n-2]<<b[nums.size()-1];
return max(a[n-2],b[nums.size()-1]);
}
丑数
三指针法:因为丑数只是因数只有2,3,5的数;
所以先设置一个dp数组表示当前已经找出并排列好的丑数数组;
再3个指针:i,j,k 表示 将当前已经排好的数组元素 *2,*3,*5 所得的数组(实际上,因为所得的数字最终会加入唯一的排序数组,所以并不用真正将3个数组建立起来),
因为排序后数组*2,*3,*5得到的也是已经排序数组,接下来的目的就是从这3个虚拟数组中的头元素中取得最小那个值;(实际上并不会出现3个数组)
所以关键就是如何用这3个指针进行选择,
实际上排序并不是在加入排序数组后才整理排序,而是每次用老元素*2,*3,*5生成新元素的时候,选择最小的加入达到排序目的,这个时候指针移动就起到作用了,如下过程所示:
已经排好的数组,先放进第一个丑数1:[1]
排好数组*2 :[1]*2=2
排好数组*3 :[1]*3=3
排好数组*5 :[1]*5=5
选结果中最小的数加入数组,此时明显是2,新的排序数组为[1,2];
已经排好的数组:[1,2]------此时上次结果中,2,3,5只用到了2;但3明显是这次需要加入的数;既然2已经加入,再去对比2没意义了,那么就将 (2的指针) [1]转向下一位[2],对比 (下一个元素2 和3,5)的大小,取最小的加入;
排好数组*2 :[2]*2=4
排好数组*3 :[1]*3=3
排好数组*5 :[1]*5=5
选结果中最小的数加入数组,此时明显是2,新的排序数组为[1,2,3];
已经排好的数组:[1,2,3]------3已经加入,移动它的指针到排序数组下一位;
排好数组*2 :[2]*2=4
排好数组*3 :[2]*3=6
排好数组*5 :[1]*5=5
选结果中最小的数加入数组,此时明显是4,新的排序数组为[1,2,3,4];
依次类推;可以看到,实际上,只需将指针进行分别移动就可以了;所以要做出3个指针;
维护3个值val2,val3,val5,表示将当前排列好数组 分别*2,*3,*5所得的结果数;
因为可能3组数出现重复现象,所以要判断是否重复后再加入
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> dp(n + 1);
dp[1] = 1;
int p2 = 1, p3 = 1, p5 = 1;
for (int i = 2; i <= n; i++) {
int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
dp[i] = min(min(num2, num3), num5);
if (dp[i] == num2) {
p2++;
}
if (dp[i] == num3) {
p3++;
}
if (dp[i] == num5) {
p5++;
}
}
return dp[n];
}
};
链表
从尾到头打印链表
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> v;
ListNode *p=head;
if(head==NULL)
return v;
while(p->next){
v.insert(v.begin(),p->val);
p=p->next;
}
v.insert(v.begin(),p->val);
return v;
}
};
两个链表的第一个公共节点
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode *> visited;
ListNode* temp=headA;
while(temp){
visited.insert(temp);
temp=temp->next;
}
temp=headB;
while(temp){
if(visited.count(temp))
return temp;
temp=temp->next;
}
return NULL;
}
时间复杂度:O(m+n)
空间复杂度:O(m)
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA==NULL||headB==NULL)return NULL;
ListNode *p=headA;
ListNode *q=headB;
while(p!=q){
p=p==NULL?headB:p->next;
q=q==NULL?headA:q->next;
}
return p;
}
时间复杂度:O(m+n)
空间复杂度:O(1)
判断回文链表
bool isPalindrome(ListNode* head) {
if(head==NULL) return true;
ListNode* p=head;
int i=0;
while(p){
i++;p=p->next;
}
stack<int> q;
int x=i/2;
while(x--){
q.push(head->val);
head=head->next;
}
if(i%2!=0)head=head->next;
while(head&&!q.empty()){
if(q.top()!=head->val)return false;
head=head->next;
q.pop();
}
if(!q.empty()||head)return false;
return true;
}
删除有序链表重复元素
ListNode* deleteDuplicates(ListNode* head) {
ListNode* p=head;
if(head==NULL)return NULL;
while(p->next&&p){
if(p->val==p->next->val){
p->next=p->next->next;
continue;
}
p=p->next;
}
return head;
}
输出链表倒数第K个节点
方法1
ListNode* getKthFromEnd(ListNode* head, int k) {
vector<ListNode*> vec;
while(head){
vec.push_back(head);
head=head->next;
}
return vec[vec.size()-k];
}
方法2:快慢指针,让快指针先走k步,k–,k小于等于0退出循环,慢指针出发,快指针遍历完时,慢指针所指即是。
原地反转链表
迭代
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
递归
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) {
return head;
}
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
链表排序
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
ListNode* sortList(ListNode* head) {
return sortList(head, nullptr);
}
ListNode* sortList(ListNode* head, ListNode* tail) {
if (head == nullptr) {
return head;
}
if (head->next == tail) {
head->next = nullptr;
return head;
}
ListNode* slow = head, *fast = head;
while (fast != tail) {
slow = slow->next;
fast = fast->next;
if (fast != tail) {
fast = fast->next;
}
}
ListNode* mid = slow;
return merge(sortList(head, mid), sortList(mid, tail));
}
ListNode* merge(ListNode* head1, ListNode* head2) {
ListNode* dummyHead = new ListNode(0);
ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
while (temp1 != nullptr && temp2 != nullptr) {
if (temp1->val <= temp2->val) {
temp->next = temp1;
temp1 = temp1->next;
} else {
temp->next = temp2;
temp2 = temp2->next;
}
temp = temp->next;
}
if (temp1 != nullptr) {
temp->next = temp1;
} else if (temp2 != nullptr) {
temp->next = temp2;
}
return dummyHead->next;
}
约瑟夫环
数组
#include<bits/stdc++.h>
using namespace std;
//用数组实现约瑟夫环问题
int a[110]={0}; //元素值为0表示未出局
//i既代表数组的下标,也代表每个人的编号
//k是用来计数的,一旦k的值达到m,代表此人需要出局,并且k需要重新计数,这样才能够找出所有需要出局的人
//数组的0代表未出局的人,数组非0代表出局的人,未出局的人需要报数,出局的人不需要报数
int main()
{
int N,M;
int cnt=0,i=0,k=0; //cnt表示目前出局的人数
cin>>N>>M; //表示总共有n人,数到数字m时出局
while(cnt!=N) //因为要求N个人的出局顺序,因此当cnt(用来统计已经出局的人)未达到n时,需要循环不断报数
{
i++; //i是每个人的编号
if(i>N) i=1; //这里需要特别注意:i的值是不断累加的,一旦发现i的值>N,那么i需要重新从第1个人开始
//数组要从第一个元素重新开始一个一个往后判断
if(a[i]==0) //只有元素值为0的人 才需要报数,元素值为非0的代表已经出局了,不用报数
{
k++;
if(k==M) //代表已经某个人已经报了M这个数,需要出局
{
a[i]=1; //编号为i的这个人出局
cnt++; //出局的人数+1
cout<<i<<" "; //输出出局的人的编号
k=0; //清空k,让下一个人重新从1开始报数
}
}
}
return 0;
}
链表
#include<bits/stdc++.h>
using namespace std;
//用链表实现约瑟夫环问题 (循环链表)
typedef struct node //typedef用来重命名struct node这种数据类型,将其命名为Node
{
int data;
struct node* next;
}Node;
void ysflb(int N,int M) //总共有N个人,报到数字为M的人出局
{
//初始化循环链表
Node *head = NULL,*p=NULL,*r=NULL; //head为头指针,指向链表的第一个结点,一开始赋值为NULL,代表不指向任何结点
head = (Node*)malloc(sizeof(Node)); //让head指向一个实际的空间
if(NULL==head) //内存空间可能会申请失败,大多数情况不会申请失败
{
cout<<"Memory Failed!";
return;
}
head->data=1; //从1开始编号
head->next=NULL; //一开始整个链表只有一个Node(结点),这个Node有两个域,分别是data和next
//data从1开始,next指向NULL,总共需要N个结点,现在创建了一个,还需要N-1个
p=head; //head要保持不能改变,才能够找到链表的起始位置,一开始p也指向第一个结点
//p等一下会被使用,用它可以便于创建剩下的N-1个结点
//尾插法创建链表,已经有一个1号结点了,还需要创建剩下的n-1个结点
for(int i=2;i<=N;i++)
{
r=(Node*)malloc(sizeof(Node));
r->data=i;
r->next=NULL;
//插入结点
p->next=r;
p=r;
}
//创建循环链表
p->next=head; //最后一个结点的next指向头结点
p=head; //为后续方便,将p指向头结点
//约瑟夫环的模拟
while(p->next!= p) //如果p的next=p,说明目前只有一个元素
{
for(int i=1;i<M;i++) //报到数字为M的时候出局
{
r=p; //保留出局的前一个结点
p=p->next; //p指向的是要出局的这个结点,需要保留前一个结点
}
// 输出
cout<<p->data<<" ";
r->next=p->next; //删除p的目的,此时p指向哪里? :
p=p->next; //更新p重新进行报数
}
cout<<p->data;
}
int main()
{
ysflb(10,3);
return 0;
}
递归
#include<bits/stdc++.h>
using namespace std;
//用递归实现约瑟夫环问题
int ysfdg(int N,int M,int i)
{
if(i==1)
{
return (M-1+N)%N;
}
return (ysfdg(N-1,M,i-1)+M)%N;
}
int main()
{
int N,M;
cin>>N>>M; //10 3
for(int i=1;i<=N;i++)
cout<<ysfdg(N,M,i)<<" ";
return 0;
}
系统设计
停车系统设计
class ParkingSystem {
public:
int big;
int medium;
int small;
ParkingSystem(int big, int medium, int small) {
this->big=big;
this->medium=medium;
this->small=small;
}
bool addCar(int carType) {
switch(carType){
case 1:if(big>0){big--;return true;}return false;break;
case 2:if(medium>0){medium--;return true;}return false;break;
case 3:if(small>0){small--;return true;}return false;break;
}
return false;
}
};
作业调度
最小加工时间
问题:假设有n个作业,m台机器设备,每个作业i可选择一台设备加工,加工时间为Ti,每一台设备只加工一个作业,基于贪心策略写程序,实现作业调度,使n个作业等待时间的和最小
#include <stdio.h>
#include <algorithm>
#include <climits>
using namespace std;
bool cmp(int &a, int &b){
return a>b;
}
int timeConsuming(int jobs[], int n, int m){
int working[m], i=0, times=0;
if(m>=n)
return jobs[0];
for( ; i<m; i++) //初始化工作列表
working[i] = jobs[i];
while(i<n){ //还有需要加工
sort(working, working+m); //工作列表 升序排序
int min = working[0]; //工作列表 最小值(最快完成的工作)
times += min;
for(int j=0; j<m; j++) //模拟减去该时间
working[j] -= min;
int k=0;
while(working[k]==0 && i<n){ //在空出来的工作台进行加工
working[k] = jobs[i];
i++;
}
}
int max = INT_MIN;
for(int j=0; j<m; j++) //找出工作列表中的最大值(最后一个完成的作业)
if(working[j]>max)
max = working[j];
times += max;
return times;
}
int main() {
int n, m;
printf("How many machines:");
scanf("%d", &m);
printf("How many jobs:");
scanf("%d", &n);
int jobs[n];
printf("Enter the times for each job:\n");
for (int i = 0; i < n; ++i)
scanf("%d", &jobs[i]);
sort(jobs, jobs+n, cmp); //降序排序
int times = timeConsuming(jobs, n, m);
printf("The total time consuming is : ", times);
return 0;