leetcode
206.反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思路:
迭代
遍历链表时,将当前节点的next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev = NULL;
ListNode *curr = head;
while(curr){
ListNode *next = curr->next; //先将当前节点curr的下面链表放在next上
curr->next = prev; //将curr的下一链表做空
prev = curr; //将curr(单个)放到prev上
curr = next; //将curr->next还回去
}
return prev; //最后返回的是prev
}
};
递归
1->2->3->4->5
理解:递归从5开始reverseList(5),返回5,reverseList(4)中p=5,head->next->next = head相当于5->next = 4, 总节点情况为4->5->4。
下一行代码 4->next = null,这时的p节点是5->4->null.
ListNode* reverseList(ListNode* head){
if(head == NULL || head->next ==NULL){
return head;
}
ListNode *p = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return p;
}
伪代码
reverseList: head=1
reverseList: head=2
reverseList: head=3
reverseList:head=4
reverseList:head=5
终止返回
cur = 5
4.next.next->4,即5->4
cur=5
3.next.next->3,即4->3
cur = 5
2.next.next->2,即3->2
cur = 5
1.next.next->1,即2->1
最后返回cur
234.回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
思路1
1.使用快慢指针找到链表中间的值,2.使用反转链表将后半部分的值反转,3.将前部分和后部分链表进行比较。
这个代码还没有还原链表, 还原链表就是在slow->next后面将prev链表再做一次反转
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
//快慢指针
ListNode *slow,*fast,*prev;
slow = head, fast = head, prev=nullptr;
while(fast){
slow=slow->next;
fast=fast->next ? fast->next->next:fast->next;
}
while(slow){ //反转 使用迭代
ListNode *temp = slow->next;
slow->next = prev;
prev = slow;
slow = temp;
}
while(head && prev){
if(head->val != prev->val){
return false;
}
head = head->next;
prev = prev->next;
}
return true;
}
};
思路2
将链表放进列表中,通过索引对列表前后节点判断是否相同。
bool isPalindrome(ListNode* head){
vector<int> vals;
while(head){
vals.emplace_back(head->val);
head = head->next;
}
for(int i=0,j=(int)vals.size()-1;i<j;++i,--j){
if(vals[i] != vals[j]){
return false;
}
}
return true;
}
时间复杂度O(n), 空间复杂度O(n)
114.二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list
思路1
- 首先将根节点的左子树变成链表
- 其次将根节点的右子树变成链表
- 最后将变成链表的右子树放在变成链表的左子树的最右边(也就是将左子树放在root的右边)
这是一个递归过程,不管函数内部细节是如何处理的,只看其函数作用以及输入与输出。对于函数flatten来说:
- 函数作用:将一个二叉树,原地展开为链表
- 输入:树的根节点
- 输出:无
void flatten(TreeNode* root){
if(root == null) return;
flatten(root->left);
flatten(root->right);
TreeNode *temp = root->right;
//把左子树放在右边
root->right = root->left;
root->left = nullptr; //把树的左边置空
while(root->right != nullptr) root = root->right;//找到树的最右边
//把原本右子树放回去
root->right = temp;
}
其他方法待写…
96.不同的二叉搜索树
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
链接:https://leetcode-cn.com/problems/unique-binary-search-trees/
思路:二叉搜索树是指左节点比根节点都小,右节点比根节点都大的二叉树。
当i为根节点的时候,左子树节点个数为[1,2,3,…,i-1],右子树节点个数为[i+1,i+2,…,n],所以当i为根节点时,左边有i-1个节点,右边有n-i个节点。
动态规划。假设n个节点存在二叉树的个数是G(n),其中1,2,…,n为根节点。当1为根节点时,其左子树节点个数为0,右子树节点个数为n-1,所以可得,G(n)=G(0)*G(n-1)+G(1)*G(n-2)+…+G(n-1)G(0)
int numTrees(int n){
vector<int> dp(n+1);
dp[0]=1,dp[1]=1;
for(int i=2;i<n+1;i++){
for(int j=1;j<i+1;j++){
dp[i] += dp[j-1]*dp[i-j];
}
}
return dp[n];
}
时间复杂度O(n^2) 空间复杂度O(n)
647.回文子串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:“abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
链接:https://leetcode-cn.com/problems/palindromic-substrings
思路1.动态规划
1.确定dp数组以及下标的含义
布尔类型的dp[i][j]:表示区间范围[i][j](左闭右闭)的子串是否是回文子串,如果是,dp[i][j]为true,否则为false
2.确定递推公式
在确定递归公式时,需要分析以下几种情况。
①s[i]与s[j]不相等,dp[i][j]一定时false
②s[i]与s[j]相等:有三种情况
情况一:i=j,指同一个字符如a,这是回文子串
情况二:i与j相差为1的时候,例如aa,也是回文子串。
情况三:i与j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文子串,aba的区间就是i+1与j-1区间,这个区间是不是回文就看dp[i+1][j-1]是否为true.
if(s[i]==s[j]){
if(j-i<=1){
dp[i][j]=true;
result++;
}else if(dp[i+1][j-1]){
result++;//统计回文子串的数量
dp[i][j]=true;
}
}
3.dp数组如何初始化
dp[i][j]全部初始化为false;
4.确定遍历顺序
要判断dp[i+1][j-1]是否为true,才能对dp[i][j]进行赋值。dp[i+1][j-1]在dp[i][j]的左下角,所以要从下到上,从左到右遍历。(也就是只计算dp矩阵的右上角的框)
int countSubstrings(string s){
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
int result=0;
for(int i=s.size()-1;i>=0;i--){
for(int j=i;i<s.size();j++){
/*
if(s[i] == s[j]){
if(j-i<=1){
result++;
dp[i][j]=true;
}else if(dp[i+1][j-1]){
result++;
dp[i][j]=true;
}
}
*/
if(s[i]==s[j] && (j-i<=1 || dp[i+1][j-1])){
result++;
dp[i][j]=true;
}
}
}
return result;
}
时间复杂度:O(n^2)
空间复杂度:O(n^2)
思路2.双指针
首先 确定回文串,就是找中心然后向两边扩散看是不是对称的。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
int extend(const string& s,int i,int j,int n){
int res=0;
while(i>=0 && j<n && s[i]==s[j]){
i--;
j++;
res++;
}
return res;
}
int countSubstring(string s){
int result = 0;
for(int i=0;i<s.size();i++){
result += extend(s,i,i,s.size());
result += extend(s,i,i+1,s.size());
}
return result;
}
461.汉明距离
汉明距离表示两个(相同长度)字对应位不同的数量,我们以d(x,y)表示两个字x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。
思路:先做与,再按位累加1的个数。
int hammingDistance(int x,int y){
x ^= y, y=0;
while(x) ++y,x&=x-1;
return y;
}
739.每日温度
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
链接:https://leetcode-cn.com/problems/daily-temperatures
思路:一是暴力解法,不过普通的暴力解法会超时,应该用一个数组表示不同温度的坐标。
由于温度范围在[30,100]之内,因此可以维护一个数组next记录每个温度第一次出现的下标。数组next中的元素初始化为无穷大,在遍历温度列表的过程中更新next值。
反向遍历温度列表。对于每个元素T[i],在数组next中找到从T[i]+1到100中每个温度第一次出现的下标,将其中的最小下标记为warmerIndex,则warmerIndex为下一次温度比当天高的下标。如果warmerIndex不为无穷大,则warmerIndex-i即为为下一次温度比当天高的等待天数,最后领next[T[i]]=i
vector<int> dailyTemperatures(vector<int>& T){
int len = T.size();
vector<int> res(len), next(101,INT_MAX);
for(int i=len-1;i>=0;i--){
int warmerIndex = INT_MAX;
for(int j=T[i]+1;j<=100;j++){
warmerIndex = min(warmerIndex,next[j]);
}
if(warmerIndex != INT_MAX){
res[i] = warmerIndex - i;
}
next[T[i]] = i;
}
return res;
}
二是单调栈,将温度的坐标入栈,若后面一个温度 i 比栈顶坐标s.top()的温度高,则说明该坐标(s.top())的结果为 i - s.top(),
若后面的温度小于该栈顶坐标的温度,则继续入栈。
vector<int> dailyTemperatures(vector<int>& T){
int len=T.size();
vector<int> res(len);
stack<int> s;
for(int i=0;i<len;i++){
while(!s.empty() && T[i] > T[s.top()]){
int index = s.top();
res[index] = i-index;
s.pop();
}
s.push(i);
}
}
42.接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
https://leetcode-cn.com/problems/trapping-rain-water/
1.暴力解法
每个柱子顶部可以储水的高度为:该柱子的左右两侧最大高度的最小值减去当前柱子的高度。然后累加。
若当前柱子太高,就无法储水。
#include<iostream>
#include<vector>
#include<math.h>
using namespace std;
int trap(vector<int>& arr){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
int len = arr.size();
if(len==0) return 0;
//cout << len << endl;
int res = 0;
for(int i=1;i<len-1;i++){
int leftMax = 0, rightMax = 0;
for(int j=0;j<i+1;j++){
leftMax = max(leftMax,arr[j]);
}
for(int j=i;j<len;j++){
rightMax = max(rightMax,arr[j]);
}
res += min(leftMax,rightMax) - arr[i];
}
return res;
}
int main(){
vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
cout << trap(arr) << endl;
return 0;
}
时间 O(n^2) 空间O(1)
2.动态规划
暴力解法中有很多的重复计算,最大高度可以记忆化。假如我们使用两个数组maxleft和maxright,maxleft[i]表示下标i左边最高柱子的高度,maxright[i]表示下标i右边最高柱子的高度。很明显,一次遍历就可以得到结果。
也就是常说的动态规划。
#include<iostream>
#include<vector>
#include<math.h>
using namespace std;
int trap(vector<int>& arr){
int len = arr.size();
if(len==0) return 0;
vector<int> leftmax(len);
vector<int> rightmax(len);
int res = 0;
leftmax[0] = arr[0];
rightmax[len-1] = arr[len-1];
for(int i=1;i<len;i++){
leftmax[i] = max(leftmax[i-1],arr[i]);
}
for(int i=len-2;i>=0;i--){
rightmax[i] = max(rightmax[i+1],arr[i]);
}
for(int i=0;i<len;i++){
res += min(leftmax[i],rightmax[i]) - arr[i];
}
return res;
}
int main(){
vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
cout << trap(arr) << endl;
return 0;
}
时间 O(n) 空间O(n)
3.单调栈
#include<iostream>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;
int trap(vector<int>& arr){
int len = arr.size();
if(len==0) return 0;
int res = 0;
stack<int> s; //这是一个单调递减的栈
for(int i=0;i<len;i++){
while(!s.empty() && arr[i] > arr[s.top()]){ //找出比栈顶大的栈,这样就可以接到雨水
int cur = s.top(); //栈顶值就是当前的最低的点
s.pop(); //让底部出栈
if(s.empty()) break;
int l = s.top(); //左边最低的栈
int r = i;
int h = min(arr[r],arr[l]) - arr[cur];
res += (r-l-1) * h;
}
//对最低的柱子入栈
s.push(i);
}
return res;
}
int main(){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
vector<int> arr = {4,2,0,3,2,5};
cout << trap(arr) << endl;
return 0;
}
84.柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。链接
暴力解法
找出当前柱子左右最大的值的坐标,得到宽度,当前柱子的长度为高
#include<iostream>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;
int largestRectangleArea(vector<int>& arr){
int len = arr.size();
if(len==0) return 0;
int res = 0;
for(int i=0;i<len;i++){
int w = 1, h = arr[i], j=i;
while(--j>=0 && arr[j] >= h){
w++;
}
j = i;
while(++j <len && arr[j] >= h){
w++;
}
res = max(res, w * h );
}
return res;
}
int main(){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
vector<int> arr = {4,2,0,3,2,5};
cout << largestRectangleArea(arr) << endl;
return 0;
}
单调栈
思路:链接
#include<iostream>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;
int largestRectangleArea(vector<int>& arr){
arr.push_back(0);
int len = arr.size();
if(len==0) return 0;
int res = 0;
stack<int> s; //维护一个单调递增栈
for(int i=0;i<len;i++){
while(!s.empty() && arr[i] < arr[s.top()]){
int index = s.top();
s.pop();
res = max(res, arr[index] * (s.empty()? i : (i-s.top() - 1)));
}
s.push(i);
}
return res;
}
int main(){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
vector<int> arr = {4,2,0,3,2,5};
cout << largestRectangleArea(arr) << endl;
return 0;
}
238.除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
链接:https://leetcode-cn.com/problems/product-of-array-except-self
暴力解法
超时
#include<iostream>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;
vector<int> productExceptSelf(vector<int>& nums){
vector<int> res(nums.size(),0);
for(int i=0;i<nums.size();i++){
int ans = 1;
for(int j=0;j<nums.size();j++){
if(i!=j){
ans *= nums[j];
}
}
res[i] = ans;
}
return res;
}
int main(){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
vector<int> arr = {4,2,5};
vector<int> res = productExceptSelf(arr);
for(auto e:res)
cout << e << ' ' <<endl;
return 0;
}
左右乘积
思路:先算每个值左边的乘积和,然后再从后开始遍历,算右边的乘积。
#include<iostream>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;
vector<int> productExceptSelf(vector<int>& nums){
vector<int> res(nums.size(),0);
int k = 1;
for(int i=0;i<nums.size();i++){
res[i] = k;
k = k * nums[i];
}
k=1;
for(int i=nums.size()-1;i>=0;i--){
res[i] *= k;
k *= nums[i];
}
return res;
}
int main(){
//vector<int> arr = {0,1,0,2,1,0,1,3,2,1,2,1};
vector<int> arr = {4,2,5};
vector<int> res = productExceptSelf(arr);
for(auto e:res)
cout << e << ' ' <<endl;
return 0;
}