leetcode热题100道(2)

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值