算法题目总结
递归:
青蛙跳楼梯的题目:一共n阶的楼梯需要爬,一次可以爬1或者2个,一共有几种方法。
public static int dump(int n){
if (n <= 2){
return n;
}
int a = 1,b = 1,c = 0;
for (int i = 1;i < n;i ++){
c = a + b;
b = a;
a = c;
}
return a;
}
public static int dump(int n){
int a = 0,b = 0,c = 1;
for (int i = 0;i < n;i ++){
a = b;
b = c;
c = a + b;
}
return c;
}
- 112:路径总和:求二叉树根节点到叶节点有没有和为目标值targetSum的路径存在
这里也是一个递归的方法,这道题一上来的感觉就是很麻烦,算不上难,但是如果直接上手递归的方法,就OK啦。
把所有的节点遍历一遍,只要保证是向下遍历即可,也就是向左右子节点走。且值也需要减掉父节点值。最后的返回的是该节点既无左右子节点,且值还为最终剩余的节点值。
public static void find(ListNode root,int target){
if (root == null){
return false;
}
if (root.left == null && root.right == null && root.val == target){
return true;
}
return find(root.left,target - root.val) || find(root.right,target - root.val);
}
这个算法思想就不用多说了,直接对其进行调用叠加就行。
public static int fib(int n){
if (n <= 1){
return n;
}
return fib(n-1)+fib(n-2);
}
分治:
本次使用的是分治法,也就是将k个链表分为单个链表进行连接,然后合并。思想有点像:
? 将10个链表分为5组链表,每组两个,每组先进行排序,然后形成5个更长的链表,然后将五个链表分为3组(有一个无序拼接),就形成了三个更更长的链表,最后形成一个最长的链表。也就结束了。
public ListNode mergerKLists(ListNode[] lists){
if (lists == null || lists.length == 0){
return null;
}
return merger(lists,0,lists.length -1);
}
public ListNode merger(ListNode[] lists,int left,int right){
if (left == right) return lists[left];
int mid = left + (right - left) / 2;
ListNode l1 = merger(lists,left,mid);
ListNode l2 = merger(lists,mid+1,right);
return mergerTwoList(l1,l2)
}
public ListNode mergerTwoList(ListNode l1,ListNode l2){
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val < l2.val){
l1.next = mergerTwoList(l1.next,l2);
}else{
l2.next = mergerTwoList(l1,l2.next);
}
}
这道题就是求相同的数大于整个数组一半的元素,这里的方法为,使用hashmap来存储数据,这样的好处是,可以先判断hash的key值存储数字,value存储次数。
每次加入时,只需要简单的判断,如果在hash中,就get到它的value,然后+1即可,如果没有存在的话,就直接添加并赋value为1。
public static void find(int[] arr){
HashMap<Integer,Integer> hash = new HashMap<>();
for (int i = 0;i < arr.length;i ++){
if (hash.containsKey(arr[i])){
hash.put(arr[i],hash.get(arr[i])+1);
}else{
hash.put(arr[i],1);
}
}
int len = arr.length / 2;
Set<Integer> keySet = hash.keyset();
for (Integer i : keyset){
if (i > len){
System.out.println(hash.getKey(i));
}
}
}
一个从左到右的升序,每列从小到大排列,每行也是从小到大排序。给定一个数字,判断该数组中是否存在该数组
public static boolean find(int[][] nums,int target){
for (int i = 0;i < nums[0].length;i ++){
if (nums[0][i] > target){
return false;
}
for (int j = 0;j < nums.length;j ++){
if (nums[j][i] == target){
return true;
}else if (nums[j][i] > target){
break;
}
}
}
return false;
}
单调栈:
给定n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻且宽度为1.
public int largesArea(int[] heights){
int len = heights.length;
Stack<Integer> stack = new Stack<>();
int[] arr = new int[len + 2];
for (int i = 0;i < len;i ++){
arr[i+1] = heights[i];
}
int area = 0;
for (int j = 0;j <= len+1;j ++){
while(!stack.isEmpty() && arr[stack.peek()] > arr[j]){
int height = arr[stack.pop()];
int l = stack.peek();
area = Math.max(area,(j - l + 1)*height);
}
stack.push(j);
}
return area;
}
- 85:求二维数组中的最大矩阵面积,当然可以利用上一题的计算。
这道题定义同样也为 难 ,但是仔细考虑这道题就会发现也是使用单调栈的思想进行计算,唯一不同的是,这道题の计算不简简单单是计算一次,而是每行至最顶层的区间都要计算,就是从顶层,依次向下移动一行,但是上面不动。
这样就很好理解了,是不是发现,就变成上面的单调栈计算了。但是有一点需要注意的是,加入该层不存在’1‘,那么它的高度就为0.
public static int maxRectangle(char[][] matrix){
if (matrix.length = 0){
retrurn 0;
}
int area = 0;
int[] row = new int[matrix[0].length];
for (int i = 0;i < matrix.length;i ++){
for (int j = 0;j < matrix[0].length;j ++){
if (matrix[i][j] == '1'){
row[j] += 1;
}else{
row[j] = 0;
}
}
area = Math.max(area,largeArea(row));
}
return area;
}
- 739:求每日温度中,每天需要再等几天就能到更高的温度。
也就是说温度如所示:[23,25,27,25,24,26,28]。其中第一天23到第二天温度就高于23度了,所以第一天为1,同理第二天也是1,第三天27℃,后面都小于它直到28℃,那么第三天就为4,依次类推
public static int findHigh(int[] nums){
int len = nums.length();
Stack<Integer> stack = new Stack<>();
int[] arr = new int[len];
for (int i = 0;i < len;i ++){
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]){
int pre = stack.pop();
arr[pre] = i - pre;
}
stack.push(i);
}
return arr;
}
给定一个循环的数组(最后一个元素的下一个元素为第一个元素),输出每一个元素の下一个更大的元素。
public static int find(int[] nums){
int len = nums.length;
int[] arr = new int[len];
Stack<Integer> stack = new Stack<>();
Arrays.fill(-1);
for (int i = 0;i < len;i ++){
while (! stack.isEmpty() && nums[stack.peek()] < nums[i % len]){
int temp = stack.pop();
arr[temp%len] = nums[i%len];
}
stack.push(i%len);
}
return arr;
}
并查集:
也就是找寻图中有几块相互不连通的区域,我们可以通过很多的方式来找寻,比如深度优先算法,广度优先算法以及并查集。
public staic void find(int[][] connected){
int n = connected.length;
UnionFind uf = new UnioFind(n);
for (int i = 0;i < n;i ++){
for (int j = i + 1;j < n;j ++){
if (connected[i][j] == 1){
uf.unio(i,j);
}
}
}
return uf.size;
}
class UnionFind{
int[] roots;
int size;
public UnionFind(int n){
roots = new int[n];
for (int i = 0;i < n;i ++){
roots[i] = i;
}
size = n;
}
public int find(int i){
if (i == roots[i]){
return i;
}
return roots[i] = find(root[i]);
}
public void unio(int p,int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot != qRoot){
roots[pRoot] = qRoot;
size--;
}
}
}
给一个有’1‘和’0‘组成的二维网格,计算网格中的岛屿数量,直接使用深度优先遍历即可
public static void find(int[][] land){
int row = land.length;
int col = land[0].length;
int count = 0;
for (int i = 0;i < row;i ++){
for (int j = 0;j < col;j ++){
if (land[i][j] == 1){
count++;
dfs(i,j,land);
}
}
}
public static void dfs(int m,int n,int[][] land){
if (m < 0 || n < 0 || m > land.length || n > land[0].length){
return;
}
if (land[m][n] != 1){
return;
}
land[m][n] = 2;
dfs(m,n-1,land);
dfs(m-1,n,land);
dfs(m,n+1,land);
dfs(m+1,n,land);
}
}
找寻这个图变成有环图的第一根边,并将其返回,无则返回[0,0];
int[] parents;
public int[] findFirstConnection(int[][] edges){
if (edges == null || edges.length == 0) return int[]{0,0};
int n = edges.length + 1;
init(n);
for (int[] edge: edges){
int x = edge[0],y = edge[1];
if (!union(x,y)){
return edge;
}
}
public init(int n){
parents = new int[n];
for (int i = 0;i < n;i ++){
parents[i] = i;
}
}
public int find(int x){
if (x != parents){
parents[x] = find(parents[x]);
}
return parents[x];
}
public boolean union(int x,int y){
int xRoot = find(x);
int yRoot = find(y);
if (xRoot == yRoot){
return false;
}
parents[xRoot] = yRoot;
return true;
}
}
滑动窗口:
- LeetCode209:找出数组中大于目标数的最短长度
给定一个数组和一个目标数,找出数组中大于目标数的最短数组,并返回其长度。
public static int find(int[] nums,int target){
int left = 0,right = 0,sum = 0,min = Integer.MAX_VALUE;
while (right < nums.length){
sum += nums[right++];
while (sum >= target){
min = Math.min(min,right-left);
sum -= nums[left++];
}
}
return min == MAX_VALUE ? 0 : min;
}
这里想着使用一个hashmap来存储字符,这样就可以判断字符是否存在里面来决定是否移动
public static int fin(String s){
if (s.length() <= 0){
return 0;
}
Map<Integer,Integer> hash = new HashMap<>();
int left = 0,sum = 0;
for (int i = 0;i < s.length();i ++){
if (hash.containsKey(s.charAt(i))){
left = Math.min(left,hash.get(s.charAt(i))+1);
}else{
sum = Math.max(sum,i-left+1);
}
}
return sum;
}
这次是升级版,也就是会给出一个目标数,该数为最多将0转变为1的个数,转变后求出最长的1子串个数
public static int longestOnes(int[] nums, int k) {
if (nums.length == 0){
return 0;
}
if (nums.length <= k){
return k;
}
int zero = 0,left = 0,right = 0;
int sum = 0;
while (right < nums.length){
if (nums[right++] == 0){
zero ++;
}
while (zero > k){
if (nums[left++] == 0){
zero--;
}
}
sum = Math.max(sum,right - left);
}
return sum;
}
这道题难以理解,但是找到诀窍后就很好理解了。如“abc”和“bcd”每个字符对应的ASCII码差值为转变需要的能量,给定一个能量值,求出能量值范围内最长能转变为相近的子串。
public int equalSubstring(String s, String t, int maxCost) {
int len = s.length();
int[] arr = new int[len];
for (int i = 0;i < len;i ++){
arr[i] = Math.abs(Integer.valueOf(s.charAt(i)) - Integer.valueOf(t.charAt(i)));
}
int sum = 0,left = 0,right = 0,chang = 0;
while (right < len){
sum += arr[right++];
while (sum > maxCost){
sum -= arr[left++];
}
chang = Math.max(chang,right - left);
}
return chang;
}
前缀和:
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
public int pivoIndex(int[] nums){
int sum = 0;
for (int i = 0;i < nums.length;i ++){
sum += nums[i];
}
int proSum = 0;
for (int j = 0;j < nums.length;j ++){
if ((proSum * 2 + nums[j]) == sum){
return j;
}
proSum += nums[j];
}
return -1;
}
给定一个整数数组和一个整数 **k,**你需要找到该数组中和为 k 的连续的子数组的个数。
public int subarraySum(int[] nums, int k) {
int len = nums.length;
int[] preNum = new int[len+1];
for (int i = 0;i < len;i ++){
preNum[i + 1] = preNum[i] + nums[i];
}
int count = 0;
for (int j = 0;j < len;j ++){
for (int js = j;js < len;js ++){
if (preNum[js+1] - preNum[j] == k)
count++;
}
}
return count;
}
求一棵二叉树上一段路径的和为target,有几条这样的路段。
class Solution {
public int pathSum(TreeNode root, int sum) {
Map<Integer, Integer> prefixSumCount = new HashMap<>();
prefixSumCount.put(0, 1);
return recursionPathSum(root, prefixSumCount, sum, 0);
}
private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
if (node == null) {
return 0;
}
int res = 0;
currSum += node.val;
res += prefixSumCount.getOrDefault(currSum - target, 0);
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
res += recursionPathSum(node.left, prefixSumCount, target, currSum);
res += recursionPathSum(node.right, prefixSumCount, target, currSum);
prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
return res;
}
}
作者:burning-summer
链接:https://leetcode-cn.com/problems/path-sum-iii/solution/qian-zhui-he-di-gui-hui-su-by-shi-huo-de-xia-tian/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
数组包含多少个奇数字和给定目标数相同,这样的数组段应该有多少个
public int numberOfSubarrays(int[] nums, int k) {
int left = 0,right = 0,jishu = 0,res = 0;
while (right < nums.length){
if ((nums[right] & 1 )== 1){
jishu++;
}
if (jishu == k){
int temp = right;
while (right < nums.length && (nums[right] & 1) == 0){
right++;
}
int rightOdd = right - temp;
int leftOdd = 0;
while ((nums[left] & 1 )== 0){
leftOdd++;
left++;
}
res += (leftOdd + 1) * (rightOdd + 1);
}
jishu--;
left++;
}
return res;
}
差分:
121、122 拓扑排序:LeetCode210
字符串:
就是一段字符串中最长的回文字符串,返回其长度
就是给出一些括号的标志,求出是否合法。加入栈的思想,如下所示:
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char ss:s.toCharArray()){
if (ss == '('){
stack.push(')');
}else if (ss == '['){
stack.push(']');
}else if (ss == '{'){
stack.push('}');
}else if (stack.isEmpty() || ss != stack.pop()){
return false;
}
}
return stack.isEmpty();
}
其实就和我们小时候一样去计算,将两个数一个一个的拆分出来,用第二位的每一位去遍历相乘第一位的每一位。
需要注意的点:第一位可能存储的是0,所以要清除它
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0"))
return "0";
int sum = 0;
int len1 = num1.length();
int len2 = num2.length();
int[] arr = new int[len1 + len2];
for (int i = len2 - 1;i >= 0;i --){
int index2 = num2.charAt(i) - '0';
for (int j = len1 - 1;j >= 0;j --){
int index1 = num1.charAt(j) - '0';
sum = arr[i + j + 1] + index1 * index2;
arr[i + j + 1] = sum % 10;
arr[i + j] += sum/10;
}
}
StringBuilder str = new StringBuilder();
for (int i = 0;i < len1 + len2;i ++){
if (i == 0 && arr[i] == 0) continue;
str.append(arr[i]);
}
return str.toString();
}
二分查找:LeetCode33、34
BFS:
有点像最小单词转换路径消耗
130、529、815
DFS&回溯:
思路是,先使用dfs找出所有的桥,然后,使用bfs不断向外扩张,然后检验扩张的格子是否含有桥,如果有,那么就到达桥了,直接返回扩张了多少次。
找出有向图中,将树型结构变为图结构的那条边
class Solution {
private:
static const int N = 1010; // 如题:二维数组大小的在3到1000范围内
int father[N];
int n; // 边的数量
// 并查集初始化
void init() {
for (int i = 1; i <= n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return ;
father[v] = u;
}
// 判断 u 和 v是否找到同一个根
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 在有向图里找到删除的那条边,使其变成树
vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
init(); // 初始化并查集
for (int i = 0; i < n; i++) { // 遍历所有的边
if (same(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
return edges[i];
}
join(edges[i][0], edges[i][1]);
}
return {};
}
// 删一条边之后判断是不是树
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
init(); // 初始化并查集
for (int i = 0; i < n; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int inDegree[N] = {0}; // 记录节点入度
n = edges.size(); // 边的数量
for (int i = 0; i < n; i++) {
inDegree[edges[i][1]]++; // 统计入度
}
vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
// 找入度为2的节点所对应的边,注意要倒叙,因为优先返回最后出现在二维数组中的答案
for (int i = n - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
vec.push_back(i);
}
}
// 处理图中情况1 和 情况2
// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
if (vec.size() > 0) {
if (isTreeAfterRemoveEdge(edges, vec[0])) {
return edges[vec[0]];
} else {
return edges[vec[1]];
}
}
// 处理图中情况3
// 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
return getRemoveEdge(edges);
}
};
找出矩阵从0,0走到右下角的最高得分路径,可以使用动态规划,也可以使用dfs,这里使用dfs
int[][] dic = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
public static void DFS(int i, int j, int t,int[][] A,int[][] v){
int len1 = A.length;
int len2 = A[0].length;
if(i == len1-1 && j == len2-1){
res = Math.max(res, t);
}
for(int index = 0; index<4; index++){
int x = i + dic[index][0];
int y = j + dic[index][1];
if(x>=0 && x<len1 && y>=0 && y<len2 && v[x][y]==0){
v[x][y]=1;
DFS(x,y,A[x][y],A,v);
v[x][y]=0;
}
}
}
求孤独像素的个数,孤独像素的意思就是1,在它的列和都没有相同的数字
class Solution {
List<List<Integer>> res = new LinkedList<List<Integer>>();
Deque<Integer> arr = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
findPath(root,targetSum);
return res;
}
public void findPath(TreeNode root,int target){
if (root == null){
return;
}
arr.offerLast(root.val);
target -= root.val;
if (root.left == null && root.right == null && target == 0){
res.add(new LinkedList<Integer>(arr));
}
findPath(root.left,target);
findPath(root.right,target);
arr.pollLast();
}
}
家变成了一个树型结构,让我们来打劫
public int rob(TreeNode root){
HashMap<TreeNode,Integer> hash = new HashMap<>();
return find(hash,root);
}
public int find(HashMap<TreeNode,Integer> hash,TreeNode root){
if (root == null){
return 0;
}
if (hash.containsKey(root)){
return map.get(root);
}
int money = root.val;
if (root.left != null){
money += (find(map,root.left.left) + find(map,root.left.right));
}
if (root.right != null){
money += (find(map,root.right.left) + find(map,root.right.right));
}
int result = Math.max(money,find(map,root.left) + find(map,root.right));
map.push(root,result);
return result;
}
动态规划:
就是说大家劫舍首位相连,也就是投了第一家就不能投最后一家,他们形成了一个闭环。
public static int find(int[] nums){
int len = nums.length;
int[] dp = new int[len];
dp[0] = nums[0];
for (int i = 1;i < len;i ++){
if (i < 2){
dp[i] = Math.max(dp[i-1],dp[i-2]+0);
}else{
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
}
return dp[len-1];
}
public static void main(String[] args){
int[] nums = {1,24,5,3,12,43};
int result = Math.max(find(String.copyOfRange(0,len-1,nums)),find(String.copyOfRange(1,len,nums)))
}
这是一个限次数的购买股票的方法,当然这样的股票购买方法就可以延伸到k次购买机会,如果不限次数的购买股票的方法,那么就可以选择隔天购买的方法。或者动态规划。
public int maxProfitDP(int[] prices) {
if (prices == null || prices.length <= 1) return 0;
int[][][] dp = new int[prices.length][2][3];
int MIN_VALUE = Integer.MIN_VALUE / 2;
dp[0][0][0] = 0;
dp[0][0][1] = dp[0][1][1] = MIN_VALUE;
dp[0][0][2] = dp[0][1][2] = MIN_VALUE;
dp[0][1][0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = Math.max(dp[i - 1][1][0] + prices[i], dp[i - 1][0][1]);
dp[i][0][2] = Math.max(dp[i - 1][1][1] + prices[i], dp[i - 1][0][2]);
dp[i][1][0] = Math.max(dp[i - 1][0][0] - prices[i], dp[i - 1][1][0]);
dp[i][1][1] = Math.max(dp[i - 1][0][1] - prices[i], dp[i - 1][1][1]);
dp[i][1][2] = MIN_VALUE;
}
return Math.max(0, Math.max(dp[prices.length - 1][0][1], dp[prices.length - 1][0][2]));
}
就是从左上角走到右下角得所有可能路径,只能向下和向右走。这里使用动态规划,也就是起点到该点的所有路径。
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0;i < m;i ++){
dp[i][0] = 1;
}
for (int j = 0;j < n;j ++){
dp[0][j] = 1;
}
dp[0][0] = 1;
for (int i = 1;i < m;i ++){
for (int j = 1;j < n;j ++){
dp[i][j] = dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
初步的想法是,将有障碍物的网格删除至为0。
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
for (int i = 0;i < m;i ++){
if (obstacleGrid[i][0] == 1){
break;
}
dp[i][0] = 1;
}
for (int j = 0;j < n;j ++){
if (obstacleGrid[0][j] == 1){
break;
}
dp[0][j] = 1;
}
for (int i = 1;i < m;i ++){
for (int j = 1;j < n;j ++){
if (obstacleGrid[i][j] == 0){
dp[i][j] = dp[i][j-1]+dp[i-1][j];
}
}
}
return dp[m-1][n-1];
}
这还是个会员问题,就是一个炸弹在空地爆炸最多能够炸死多少人,遇到墙和边界就停止杀伤,且只能走上下左右的直线方向。
public static void main(){
}
不规则硬币,在抛掷过程中正反概论不一样,求k个正面的概论是多少?
贪心算法:
就是遍历每一个节点,将它能从起点开始跳跃的最大距离计算处理,得出下面的解使用贪心算法求解最合适,也就是说通过逐步的求解来计算能否跳过终点
public boolean canJump(int[] nums) {
int len = nums.length;
int rightMost = 0;
for (int i = 0;i < len;i ++){
if (i <= rightMost){
rightMost = Math.max(rightMost,i + nums[i]);
if (rightMost >= len - 1){
return true;
}
}
}
return false;
621、452 字典树:LeetCode820、208、648
|