3大经典算法思想

文章目录

  • 1. 动态规划算法
    • 例题1: 爬楼梯
    • 例题2: 最长递增子序列
    • 例题3: 买卖股票的最佳时机
  • 2. 贪心算法
    • 例题1: 零钱兑换
    • 例题2: 分发饼干
    • 例题3: 买卖股票的最佳时机 II
  • 3. 分治算法
    • 例题1: 求解数组中的逆序对数量
    • 例题2: 在排序数组中查找元素的第一个和最后一个位置
    • 例题3: 求解最大子数组和

1. 动态规划算法

例题1: 爬楼梯

问题描述:有一楼梯,你每次可以爬1个或2个台阶,求爬到第n个台阶有多少种不同的方法。
解决思路:使用动态规划,定义一个数组dp[n+1],dp[i]表示爬到第i个台阶的方法数。显然,dp[0]=1,dp[1]=1,dp[2]=2。对于i>2,dp[i] = dp[i-1] + dp[i-2]。
时间复杂度:O(n),空间复杂度:O(n)。

public class ClimbingStairs {
    public int climbStairs(int n) {
        if (n <= 2) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

例题2: 最长递增子序列

问题描述:给定一个无序的整数数组,找到其中最长且递增的子序列的长度。
解决思路:使用动态规划,定义一个数组dp[n],dp[i]表示以第i个元素结尾的最长递增子序列的长度。对于每个元素nums[i],遍历其前面所有元素nums[j],如果nums[i] > nums[j],则更新dp[i] = max(dp[i], dp[j] + 1)。
时间复杂度:O(n^2),空间复杂度:O(n)。

public class LongestIncreasingSubsequence {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int maxLength = 1;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxLength = Math.max(maxLength, dp[i]);
        }
        return maxLength;
    }
}

例题3: 买卖股票的最佳时机

问题描述:给定一个数组prices,它的第i个元素prices[i]表示第i天的股票价格。你只能选择某一天买入该股票,并选择在未来的某一天卖出该股票,设计一个算法来计算你所能获取的最大利润。
解决思路:使用动态规划,定义两个变量buy和sell,分别表示截止到当前的最低购买价格和最大利润。遍历数组prices,更新buy和sell的值。
时间复杂度:O(n),空间复杂度:O(1)。

public class BestTimeToBuyAndSellStock {
    public int maxProfit(int[] prices) {
        int buy = Integer.MAX_VALUE;
        int sell = 0;
        for (int price : prices) {
            buy = Math.min(buy, price);
            sell = Math.max(sell, price - buy);
        }
        return sell;
    }
}

2. 贪心算法

例题1: 零钱兑换

问题描述:给定不同面额的硬币coins和一个总金额amount,计算可以凑成总金额所需的最少硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。
解决思路:使用贪心算法,从面额最大的硬币开始,尽可能多地使用该硬币,然后再使用次大面额的硬币,以此类推。时间复杂度:O(amount * coins.length),空间复杂度:O(amount)。

import java.util.Arrays;

public class CoinChange {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) {
                if (coin <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

例题2: 分发饼干

问题描述:给定两个数组g和s,分别表示孩子们的胃口和饼干的大小。求解最多能满足多少个孩子的胃口。
解决思路:使用贪心算法,先将胃口数组g和饼干数组s排序,然后从胃口最小的孩子开始,尽可能使用最小的饼干满足其胃口,直到没有饼干或者没有孩子可以满足。
时间复杂度:O(max(mlogm, nlogn)),空间复杂度:O(1)。

import java.util.Arrays;

public class AssignCookies {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int i = 0;
        int j = 0;
        int count = 0;
        while (i < g.length && j < s.length) {
            if (g[i] <= s[j]) {
                count++;
                i++;
                j++;
            } else {
                j++;
            }
        }
        return count;
    }
}

例题3: 买卖股票的最佳时机 II

问题描述:给定一个数组prices,它的第i个元素prices[i]表示第i天的股票价格。设计一个算法来计算你所能获取的最大利润,你可以尽可能地完成更多的交易(多次买卖一支股票)。
解决思路:使用贪心算法,只要今天的价格比昨天的价格高,就进行交易。时间复杂度:O(n),空间复杂度:O(1)。

public class BestTimeToBuyAndSellStockII {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                profit += prices[i] - prices[i - 1];
            }
        }
        return profit;
    }
}

3. 分治算法

例题1: 求解数组中的逆序对数量

问题描述:给定一个整数数组nums,求解该数组中逆序对的数量。逆序对是指数组中的两个元素nums[i]和nums[j],满足i < j且nums[i] > nums[j]。
解决思路:使用分治算法,将数组一分为二,分别求解左右两个子数组中的逆序对数量,然后再计算跨越两个子数组的逆序对数量,并将其相加。时间复杂度:O(nlogn),空间复杂度:O(n)。

public class ReversePairs {
    public int reversePairs(int[] nums) {
        return mergeSort(nums, 0, nums.length - 1);
    }

    private int mergeSort(int[] nums, int left, int right) {
        if (left >= right) {
            return 0;
        }
        int mid = (left + right) / 2;
        int count = mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right);
        int[] merge = new int[right - left + 1];
        int i = left;
        int j = mid + 1;
        int k = 0;
        int p = left;
        while (i <= mid && j <= right) {
            if (nums[i] <= nums[j]) {
                merge[k++] = nums[i++];
                count += j - (mid + 1);
            } else {
                merge[k++] = nums[j++];
            }
        }
        while (i <= mid) {
            merge[k++] = nums[i++];
            count += j - (mid + 1);
        }
        while (j <= right) {
            merge[k++] = nums[j++];
        }
        System.arraycopy(merge, 0, nums, p, k);
        return count;
    }
}

例题2: 在排序数组中查找元素的第一个和最后一个位置

问题描述:给定一个按照升序排列的整数数组nums,和一个目标值target。如果target在数组中存在,则返回它的第一个和最后一个位置,否则返回[-1, -1]。
解决思路:使用分治算法,先使用二分查找找到target的位置,然后分别向左和向右进行扩展,直到找到target的第一个和最后一个位置。时间复杂度:O(logn),空间复杂度:O(1)。

public class FindFirstAndLastPositionOfElementInSortedArray {
    public int[] searchRange(int[] nums, int target) {
        int left = binarySearch(nums, target, true);
        int right = binarySearch(nums, target, false);
        return new int[]{left, right};
    }

    private int binarySearch(int[] nums, int target, boolean isLeft) {
        int left = 0;
        int right = nums.length - 1;
        int result = -1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (isLeft && nums[mid] == target)) {
                right = mid - 1;
                if (nums[mid] == target) {
                    result = mid;
                }
            } else {
                left = mid + 1;
                if (!isLeft && nums[mid] == target) {
                    result = mid;
                }
            }
        }
        return result;
    }
}

例题3: 求解最大子数组和

问题描述:给定一个整数数组nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
解决思路:使用分治算法,将数组一分为二,分别求解左右两个子数组的最大子数组和,然后再计算跨越两个子数组的最大子数组和,并将其相比较。时间复杂度:O(nlogn),空间复杂度:O(logn)。

public class MaximumSubarray {
    public int maxSubArray(int[] nums) {
        return maxSubArray(nums, 0, nums.length - 1);
    }

    private int maxSubArray(int[] nums, int left, int right) {
        if (left == right) {
            return nums[left];
        }
        int mid = (left + right) / 2;
        int leftMax = maxSubArray(nums, left, mid);
        int rightMax = maxSubArray(nums, mid + 1, right);
        int crossMax = crossMaxSubArray(nums, left, mid, right);
        return Math.max(Math.max(leftMax, rightMax), crossMax);
    }

    private int crossMaxSubArray(int[] nums, int left, int mid, int right) {
        int leftMax = Integer.MIN_VALUE;
        int leftSum = 0;
        for (int i = mid; i >= left; i--) {
            leftSum += nums[i];
            leftMax = Math.max(leftMax, leftSum);
        }
        int rightMax = Integer.MIN_VALUE;
        int rightSum = 0;
        for (int i = mid + 1; i <= right; i++) {
            rightSum += nums[i];
            rightMax = Math.max(rightMax, rightSum);
        }
        return leftMax + rightMax;
    }
}

你可能感兴趣的:(算法,算法)