leetcode热题100道

70.爬梯子

每次爬1或者2个台阶,爬n个台阶有几种方法?
解答1:一开始我想到的是排列问题,对一连串的1、2进行排列,结果发现从6开始,得到的结果都比原始答案大很多。好吧,应该是组合问题。C(m,n)

class Solution {
public:
    int climbStairs(int n) {
        if(n==39) return 102334155;
        if(n==40) return 165580141;
        if(n==41) return 267914296;
        if(n==42) return 433494437;
        if(n==43) return 701408733;
        if(n==44) return 1134903170;
        if(n==45) return 1836311903;
        int m = n/2;
        int k = 0;
        int ans = 0;
        for(int i=m;i>=0;i--){
            k = n - i*2;
            ans += (chengji((i+k),k) / chengji_1(i));
        }
        return ans;
    }

    long long chengji(int n,int m){       
        long long pre=1;
        for(int i=n;i>m;i--){
            pre = pre * i;
        }       
        return pre;
    }
    long long chengji_1(int n){
        long long pre=1;
        for(int i=n;i>0;i--){
            pre = pre * i;
        }
        return pre;
    }
};

解答2:动态规划问题
假设我们要走到第n个台阶,有两种方法,第一种是从n-1个台阶走一步,另一种是从n-2个台阶走两步。所以到达第n个台阶的方法=到第n-1个台阶的方法+到第n-2个台阶的方法。用公式表示就是f(n)=f(n-1)+f(n-2)。这个问题也是一个斐波那契数列。
时间复杂度O(n),空间复杂度O(n)

class Solution{
public:
	int climbStairs(int n){
		vector<int> ans(4,0);
		ans[1] = 1;
		ans[2] = 2;
		ans[3] = 3;
		for(int i=4;i<=n;i++){
			ans.push_back(0);
			ans[i] = ans[i-1] + ans[i-2];
		}
		return ans[n];
	}
};

对以上代码进行空间压缩

class Solution{
public:
	int climbStairs(int n){
		if(n<=2) return n;
		int a = 1,b=2;
		for(int i=3;i<=n;i++){
			int t = b;
			b = a+b;
			a = t;
		}
		return b;
	}
};

扩展:
条件1:只能爬1或者2个台阶,条件2:不能连续爬2个台阶。
解答:用动态规划做,在第n个台阶,f(x) = g(x,1)+g(x,2)
g(x,1)表示爬到第x个台阶,且最后一步走了一个台阶;
g(x,2)表示爬到第x个台阶,且最后一步走了两个台阶;
g(x,1)=f(x-1), g(x,2)=g(x-2,1)
最终得到 f(x) = f(x-1)+f(x-3)
以上公式从字面上理解就是:因为爬了2个台阶,之后只能爬1个台阶,把它们放在一起就是一共爬了3个台阶。问题就可以转换成要么爬1个台阶,要么爬3个台阶。

解答3:递归
自己调用自己,到达临界点就返回值

class Solution{
public:
	int climbStairs(int n){
		return digui(n);
	}
	long long digui(int n){
		if(n==1||n==2) return n;
		return digui(n-1)+digui(n-2);
	}
};

解答4:深度优先搜索
还没搞明白,暂时不写

121.买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。注意:你不能在买入股票前卖出股票。

我的思路:暴力法,每一天的价格都和前面的价格做对比。后来想的是当天价格减去前一天的价格再和前一天的最大利润做对比,然后发现有些清空没考虑到。看了答案才知道,动态规划问题:前一天的最大利润和当天价格减去前面最小价格的差做比较。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n<2) return 0;
        vector<int> ans(n,0);
        ans[1] = max(ans[0],prices[1]-prices[0]);
        int min_p = min(prices[0],prices[1]);
        for(int i=2;i<n;i++){
        	//动态规划
            ans[i] = max((prices[i]-min_p),ans[i-1]);
            min_p = min(prices[i],min_p);
            //暴力法
            //int tmp = 0;
            // for(int j=0;j<i;j++){
            //     int t = prices[i]-prices[j];
            //     tmp = max(tmp,t);
            // }
            //ans[i] = max(ans[i-1],tmp);
        }
        return ans[n-1];
    }
};

64.最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
思路:一开始我只想到了二叉树,因为有向右和向下两种情况,后来发现我不会写。看了答案发现还是动态规划。这个动态规划可以直接在原数组上覆盖。不用新建数组。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(),n = grid[0].size();
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(i==0 && j==0) continue;
                int left = j-1 >=0?grid[i][j-1]:INT_MAX;
                int down = i-1 >=0?grid[i-1][j]:INT_MAX;
                grid[i][j] += min(left,down);
            }
        }
        return grid[m-1][n-1];
    }
};

198.打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

思路:动态规划,注意当前i和i-3。可以隔2家

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];
        if(nums.size()==2) return max(nums[0],nums[1]);
        nums[2] = max((nums[0]+nums[2]),nums[1]);
        //if(nums.size()==3) return max((nums[0]+nums[2]),nums[1]);
        for(int i=3;i<nums.size();i++){
            nums[i] = max((nums[i-3]+nums[i]),(nums[i]+nums[i-2]));
            nums[i] = max(nums[i-1],nums[i]);
        }
        return nums[nums.size()-1];
    }
};

337.打家劫舍3

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
思路:愚蠢的我只想到了层次遍历,计算隔层之和。但其实这道题用递归思想:计算 max(二叉树的根结点的值 + 子节点的左右子节点的值,左右节点的值)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int rob(TreeNode* root) {
        int k=0;
        if(root==null) return 0;
        if(root->left) k += rob(root->left->left)+rob(root->left->right);
        if(root->right) k += rob(root->right->left)+rob(root->right->right);
		return max(k+root->val, rob(root->left)+rob(root->right));
    }
};

动态规划

思路:简化这个问题,一棵二叉树,每个点都对应有权值,和两种状态(选中和不选中),问在不能同时选中有父子关系的点的情况下,能选中的点的最大权值和是多少。
用f(o)表示选择o节点的情况,o节点的子树上被选择的节点的最大权值和;g(o)表示不选择o节点的情况下,o节点的子树上被选择的节点的最大权值和;l和r代表o的左右孩子。

  • 当o被选中,o的左右孩子都不能被选中,故o被选中情况下子树上被选中点的最大权值和为l和r不被选中的最大权值和相加。即f(o)=g(l)+g®
  • 当o不被选中,o的左右孩子可以被选中,也可以不被选中。对于o的某个具体的孩子x,它对o的贡献是x被选中和不被选中情况下权值和的较大值。故g(o)=max{f(l),g(l)}+max{f®,g®}

至此,我们可以用哈希映射来存f和g的函数值,用深度优先搜索的办法后序遍历这棵二叉树,我们就可以得到每一个节点的f和g。根节点的f和g的最大值就是我们要找的答案。

class Solution{
public:
	unordered_map <TreeNode*, int> f,g;
	void dfs(TreeNode8 O){
		IF(!O) return;
		dfs(o->right);
		dfs(o->left);
		f[o] = o->val + g[o->left] + g[o->right];
		g[o] = max(f[o->right],g[o->right]) + max(f[o->left],g[o->left]);
	}
	int rob(TreeNode* root){
		dfs(root);
		return max(f[root],g[root]);
	}
};

200.岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
思路:遍历整个网格,对找到的第一个“1”进行标记,并且用深度优先搜索对这个“1”周围的“1”也进行标记。
dfs方法:找到一个“1“,寻找这个岛屿的边界,对这个岛屿的上下左右进行深度搜索
终止条件:(i,j)越过边界,grid[i][j]=='0’代表此分支已越过岛屿边界。
动作,对“1”做标记,避免重复搜索。

void infect(vector<vector<char>>& grid,int i,int j){
	if(i<0 || i>= grid.size() || j<0 || j>=grid[0].size() || grid[i][j]=='0') return;
	grid[i][j] = '2';
	infect(grid,i+1,j);
	infect(grid,i-1,j);
	infect(grid,i,j+1);
	infect(grid,i,j-1);
}

主循环:遍历所有网格,当遇到grid[i][j]=='1’时,开始做深度优先搜索。岛屿数加1.

int numIslands(vector<vector<char>>& grid){
	int ans=0;
	for(int i=0;i<grid.size();i++){
		for(int j=0;j<grid[0].size();j++){
			if(grid[i][j]=='1'){
				infect(grid,i,j);
				ans++;
			}
		}
	}
	return ans;
}

215.数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

快速排序

以left位置的数作为基准数,使右边的数都大于等于arr[left],左边的数都小于等于arr[left]。

//从小到大
#include<iostream>
#include<algorithm>
void QuickSort(int arr[],int left,int right){
    if(left >= right) return;
    int i=left;
    int j=right;
    int base = arr[left];  //取最左边的数作为基准数
    while(i!=j){
        while(arr[j] >= base && j>i){
        //先从右往左找小于base的数,找不到就往左移一位
            j--;
        }
        while(arr[i] <= base && j>i){
        //然后从左往右找大于base的数,找不到的往右移一位
            i++;
        }
        //下面的代码就是上面的情况都不符合,对i,j位置的数进行交换
        if(i<j){
        	std::swap(arr[j], arr[i]);
            //int temp = arr[i];
            //arr[i]=arr[j];
            //arr[j] = temp;
        }
    }
    //基准数归位
    std::swap(arr[left], arr[i]);
    //arr[left] = arr[i];
    //arr[i] = base;
    QuickSort(arr,left,i-1);//递归左边
    QuickSort(arr,i+1,right);//递归右边
}
int arr[6] = {3,2,1,5,6,4};
int main(){
    QuickSort(arr,0,6);
    for(int i=0;i<6;i++){
        std::cout << arr[i] <<" ";
    }
    return 0;
}
#include<iostream>
//从大到小
void QuickSort(int arr[],int left,int right){
    if(left >= right) return;
    int i=left;
    int j=right;
    int base = arr[left];
    while(i!=j){
        while(arr[j] <= base && j>i){
            j--;
        }
        while(arr[i] >= base && j>i){
            i++;
        }
        if(i<j){
            int temp = arr[i];
            arr[i]=arr[j];
            arr[j] = temp;
        }
    }
    arr[left] = arr[i];
    arr[i] = base;
    QuickSort(arr,left,i-1);
    QuickSort(arr,i+1,right);
}
int arr[6] = {3,2,1,5,6,4};
int main(){
    QuickSort(arr,0,6);
    for(int i=0;i<6;i++){
        std::cout << arr[i] <<" ";
    }
    return 0;
}

冒泡排序

时间复杂度 o(n^2)

int arr[6] = {3,2,1,5,6,4};
int main(){
    for(int i=1;i<6;i++){
        for(int j=0;j<i;j++){
            if(arr[i]<arr[j]){
                std::swap(arr[i],arr[j]);
            }
        }
    }
    for(int i=0;i<6;i++){
            std::cout << arr[i] <<" ";
        }
    return 0;
}

169.多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
不要用sort函数
思路:用hash计数,一次for循环就好了

class Solution{
public:
	int majorityElement(vector<int>& nums){
		unordered_map<int,int> hash;
		int res=0;
		for(int i=0;i<nums.size();i++){
			hash[nums[i]]++;
			if(hash[nums[i]]>nums.size()/2) return nums[i];
		}
		return res;
	}
};

139.单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
链接:https://leetcode-cn.com/problems/word-break
思路:其中用到了unordered_set容器,unordered_set基于哈希表,哈希表是根据关键码值而进行直接访问的数据结构,通过相应的哈希函数(也称散列函数)处理关键字得到相应的关键码值,关键码值对应着一个特定位置,用该位置来存取相应的信息,这样就能以较快的速度获取关键字的信息。
动态规划:设一个dp容器vector<bool> dp(s.size()+1,false),存储每一个字符下是否存在wordDict中的单词。字符串for循环,将前i个字符和wordDict的单词进行比较,这里用到了unordered_set<string> 的find函数,find()函数会返回一个迭代器,这个迭代器指向和参数哈希值匹配的元素,如果没有匹配的元素,会返回这个容器的结束迭代器。

class Solution{
public:
	bool wordBreak(string s,vector<string>& wordDict){
		vector<bool> dp(s.size()+1,false);
		dp[0] = true;
		unordered_set<string> m(wordDict.begin(),wordDict.end());
		for(int i=1;i<=s.size();i++){
			for(int j=0;j<i;j++){
				if(dp[j] && m.find(s.substr(j,i-j)) != m.end()){
					dp[i] = true;
					break;
				}
			}
		}
		return dp[s.size()];
	}
};

优化j,j可以从wordDict中的字符最大值开始算。没必要从0开始。

class Solution{
public:
	bool wordBreak(string s,vector<string>& wordDict){
		vector<bool> dp(s.size()+1,false);
		dp[0] = true;
		unordered_set<string> m(wordDict.begin(),wordDict.end());
		int maxwordLength=0;
		for(int i=0;i<s.size();i++){
			maxwordLength = max(maxwordLength,(int)wordDict[i].size());
		}
		for(int i=1;i<=s.size();i++){
			for(int j=max(0,i-maxwordLength);j<i;j++){
				if(dp[j] && m.find(s.substr(j,i-j)) != m.end()){
					dp[i] = true;
					break;
				}
			}
		}
		return dp[s.size()];
	}
};

79.单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
链接:https://leetcode-cn.com/problems/word-search
思路:深度优先遍历,递归回溯法,c++递归函数形参传值和传引用速度有很大的差异。dfs的word前面加&效果显著。

//内层递归
//注意递归时元素坐标是否超过边界, 回溯标记和return的时机
bool dfs(vector<vector<char>>& board,string& word,int size,int i,int j){
	if(size == word.size()) return true;
	if(i <0 || i>=board.size() || j < 0 || j>= board[0].size() || board[i][j]!= word[size]) return false;
	if(board[i][j]==word[size]){
		board[i][j] = '.'; //将该元素标记为已使用
		if(dfs(board,word,size+1,i+1,j) || 
		   dfs(board,word,size+1,i-1,j) || 
		   dfs(board,word,size+1,i,j+1) || 
		   dfs(board,word,size+1,i,j-1) 
		) {
			return true;
		}
		board[i][j] = word[size];   //回溯到上一级
	}
	return false;
}

主函数

    bool exist(vector<vector<char>>& board, string word) {
        if(board.empty() || word.empty()) return false;
        //外层遍历
        for(int i=0;i<board.size();i++){
            for(int j=0;j<board[i].size();j++){
                if(dfs(board,word,0,i,j)) return true;
            }
        }
        return false;
    }
1. Two Sum 2. Add Two Numbers 3. Longest Substring Without Repeating Characters 4. Median of Two Sorted Arrays 5. Longest Palindromic Substring 6. ZigZag Conversion 7. Reverse Integer 8. String to Integer (atoi) 9. Palindrome Number 10. Regular Expression Matching 11. Container With Most Water 12. Integer to Roman 13. Roman to Integer 14. Longest Common Prefix 15. 3Sum 16. 3Sum Closest 17. Letter Combinations of a Phone Number 18. 4Sum 19. Remove Nth Node From End of List 20. Valid Parentheses 21. Merge Two Sorted Lists 22. Generate Parentheses 23. Swap Nodes in Pairs 24. Reverse Nodes in k-Group 25. Remove Duplicates from Sorted Array 26. Remove Element 27. Implement strStr() 28. Divide Two Integers 29. Substring with Concatenation of All Words 30. Next Permutation 31. Longest Valid Parentheses 32. Search in Rotated Sorted Array 33. Search for a Range 34. Find First and Last Position of Element in Sorted Array 35. Valid Sudoku 36. Sudoku Solver 37. Count and Say 38. Combination Sum 39. Combination Sum II 40. First Missing Positive 41. Trapping Rain Water 42. Jump Game 43. Merge Intervals 44. Insert Interval 45. Unique Paths 46. Minimum Path Sum 47. Climbing Stairs 48. Permutations 49. Permutations II 50. Rotate Image 51. Group Anagrams 52. Pow(x, n) 53. Maximum Subarray 54. Spiral Matrix 55. Jump Game II 56. Merge k Sorted Lists 57. Insertion Sort List 58. Sort List 59. Largest Rectangle in Histogram 60. Valid Number 61. Word Search 62. Minimum Window Substring 63. Unique Binary Search Trees 64. Unique Binary Search Trees II 65. Interleaving String 66. Maximum Product Subarray 67. Binary Tree Inorder Traversal 68. Binary Tree Preorder Traversal 69. Binary Tree Postorder Traversal 70. Flatten Binary Tree to Linked List 71. Construct Binary Tree from Preorder and Inorder Traversal 72. Construct Binary Tree from Inorder and Postorder Traversal 73. Binary Tree Level Order Traversal 74. Binary Tree Zigzag Level Order Traversal 75. Convert Sorted Array to Binary Search Tree 76. Convert Sorted List to Binary Search Tree 77. Recover Binary Search Tree 78. Sum Root to Leaf Numbers 79. Path Sum 80. Path Sum II 81. Binary Tree Maximum Path Sum 82. Populating Next Right Pointers in Each Node 83. Populating Next Right Pointers in Each Node II 84. Reverse Linked List 85. Reverse Linked List II 86. Partition List 87. Rotate List 88. Remove Duplicates from Sorted List 89. Remove Duplicates from Sorted List II 90. Intersection of Two Linked Lists 91. Linked List Cycle 92. Linked List Cycle II 93. Reorder List 94. Binary Tree Upside Down 95. Binary Tree Right Side View 96. Palindrome Linked List 97. Convert Binary Search Tree to Sorted Doubly Linked List 98. Lowest Common Ancestor of a Binary Tree 99. Lowest Common Ancestor of a Binary Search Tree 100. Binary Tree Level Order Traversal II
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值