经典算法

30 篇文章 0 订阅
6 篇文章 0 订阅

动态规划的基本思想就是将待求解问题分解为若干子问题,(如本题中我们将dp[i][j]分解为若干dp[i-1][j-x]的问题),先求解这些子问题并将结果保存起来( 我们用dp[][]二维数组保存子结果),若在求解较大的问题时用到较小子问题的结果,可以直接取用(求dp[i][j]时用dp[i-1][x]的结果),从而免去重复计算

池塘抽样算法

1. 先将前k个作为结果

2. 然后用后面的数随机替换前面的数.

    public int[] getRandom(int k, int n) {
        Random random = new Random();
       int[] res = new int[k];
       for (int i = 0; i < k; i++) {
           res[i] = i+1;
       }
       for (int i = k; i < n; i++) {
           int m = random.nextInt(i);
           res[m] = i+1;
       }
       return res;
    }

二维空间安全点

1. 将y按照从到小排序

2. 遍历数组 如果当前的x >= 前一个x则为结果.

class Point{
    int x;
    int y;

}

class MyCompare implements Comparator<Point> {

    @Override
    public int compare(Point o1, Point o2) {
        if (o1.y > o2.y) return -1;
        if (o1.y < o2.y) return 1;
        if (o1.x > o2.x) return -1;
        if (o1.x < o2.x) return 1;
        return 0;
    }
}
class MaxPoint {
    public void help (List<Point> data) {
        Collections.sort(data, new MyCompare());
        int minx = -1;
        for (Point point : data) {
            if (point.x >= minx) {
                System.out.println(point);
            }
            minx = point.x;
        }
    }
}

随机生成不重复的数

1. 先在0-n-1范围内随机生成一个数, 放入结果数组

2. 用最后一个数替换随机生成的数的位置

3. 下次在0-n-2范围内随机生成一个数,放入结果数组

 public int[] help(int n, int k) {
     int[] res = new int[k];
     int[] data = new int[n];
     for (int i = 0; i < n; i++) {
         data[i] = i+1;
     }
     Random random = new Random();
     for (int i = 0; i < k; i++) {
         int index = random.nextInt(n - i);
         res[i] = data[index];
         data[index] = data[n-1-i];
     }
     return res;
 }

组合硬币

给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法

dp[i][j]表示前i种硬币组成和为j的组合数

public int coins3(int[] arr, int aim){
    if(arr==null || arr.length==0 || aim<=0)
        return 0;
    // dp的维度 arr.length+1,多了一种不使用任何币种
    int[][] dp = new int[arr.length+1][aim+1];
    // aim=0的时候
    for(int i=0;i<=arr.length;i++)
        dp[i][0] = 1;


    for(int i=1;i<=arr.length;i++){
        for(int j=1;j<=aim;j++){
        
            // k为arr[i-1]有多少张
            for(int k=0;k<=j/arr[i-1];k++){
                dp[i][j] += dp[i-1][j - k*arr[i-1]];
            }
        }
    }
    return dp[arr.length][aim];
}

 

迷宫搜索问题(https://www.cnblogs.com/python27/p/3989435.html)

该问题在实现给定一些01方块构成的迷宫,其中1表示该方块不能通过,0表示该方块可以通过,并且给定迷宫的入口和期待的出口,要求找到一条连接入口和出口的路径。有了前面的题目的铺垫,套路其实都是一样的。在当前位置,对于周围的所有方块,判断可行性,对于每一个可行的方块,就是我们当前所有可能的choices;尝试一个choice,递归的判断是否能够导致一个solution,如果可以,return true;否则,尝试另一个choice。如果所有的choice都不能导致一个成功解,return false。剩下的就是递归终止的条件,当前所在位置如果等于目标位置,递归结束,return true。

private static final directs_x = {0, -1, 0, 1};
private static final directs_y = {-1, 0, 1, 0};

private boolean help(int[][] grid, int curx, int cury) {
        if (curx == targetX && cury == targetY) {
            return true;
        }

        for (int i = 0; i < 4; i++) {
            int nextX = curx + direct_x[i];
            int nextY = cury + direct_y[I];
            if (isSafe(grid, nextX, nextY)) {
                //仅仅做一个标志, 没有任何实际意义
                grid[nextX][nextY] = 2;
                boolean success = help(grid, nextX, nextY);
                if (success) return true;
                //回溯为可通过的值, 也就是0;
                grid[nextX][nextY] = 0;
            }
        }
        return false;
}

骑士旅行问题

在一个N*M的棋盘上,在任意位置放置一个骑士,骑士的走"日字", 问该骑士能否不重复遍历整个棋盘

    public boolean help(int[][] grid, int row, int col, int val) {
        //无路可走
        if (noWay(grid, row, col)) {
            //棋盘已经走完
            if (isFull()) {
                return true;
            }
            return false;
        }
        for (int i = 0; i < 8; i++) {
            int nextx = row + x;
            int nexty = col + y;
            //说明还在棋盘范围内
            if (isOk(grid, row, col)) {
                val++;
                grid[row][col] = val;
                boolean find = help(grid, nextx, nexty, val);
                if (find) return true;
                grid[row][col] = 0;
                val--;
            }
        }
        return false;
    }

子集和问题

给定一个数组, 问: 是否存在子集和等于给定值.

动态规划解法: 
public boolean subset(int[] a, int n){
        if (a.length == 0) return false;
        return help(a, n, a.length);
    }
    private boolean help (int[] a, int sum, int index) {
//        //减到0 说明满足
//        if (n == 0) return true;
//        //index从后往前,已经到第0个 但是n还不等于0 说明不满足
//        if (index == 0) return false;
//        //最后一个数比目标值大, 则舍弃最后一个数
//        if (a[index-1] > n) return help(a, n, index-1);
//        //最后一个数比目标值小, 可以选择包含最后一个数 也可以不包含
//        return help(a, n, index-1) || help(a, n-a[index-1], index-1);
        
        //dp[i][j] 表示 前j个元素的和是否能组成i
        boolean[][] dp = new boolean[sum+1][index+1];
        //前0个元素肯定不能组成i
        for (int i = 0; i <= sum; i++) {
            dp[i][0] = false;
        }
        //和为0 一定可以组成
        for (int i =0; i <= index; i++) {
            dp[0][i] = true;
        }
        //i表示目标 j表示元素下标
        for (int i = 1; i <= sum; i++) {
            for (int j = 1; j <= index; j++) {
                //如果目标 小于 最后j-1元素 则跳过下一个j元素
                if (i < a[j-1]){
                    dp[i][j] = dp[i][j-1];
                }else {
                    //目标大于j-1元素 可以选择包含j元素 也可以不包含
                    dp[i][j] = dp[i][j-1] || dp[i-a[j-1]][j-1];
                }
            }
        }
        //返回sum是否能被数组元素所组成
        return dp[sum][index];
}

哈密顿图问题

哈密顿图是一个无向图,由天文学家哈密顿提出,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。在图论中是指含有哈密顿回路的图,闭合的哈密顿路径称作哈密顿回路,含有图中所有顶点的路径称作哈密顿路径

/**
     * 哈密顿图问题
     * @param grid  邻接矩阵表示图中2顶点是否相连
     * @param target
     * @return
     */
    public boolean hamCycle (int[][] grid, int target) {
        int[] path = new int[grid[0].length];
        boolean[] visits = new boolean[grid[0].length];
        visits[0] = true;
        path[0] = 0;
        return help(grid, target, visits, path, 1);
    }

    private boolean help(int[][] grid, int target, boolean[] visits, int[] path, int cur) {
        if (cur == target) {
            //如果当前顶点是目标顶点 判断目标顶点是否和顶点0相连
            return grid[path[cur-1]][0] == 1;
        }
        //从1开始 是因为除了顶点0 的其他顶点
        for (int i = 1; i < target; i++) {
            //grid[path[cur-1]][i] 表示上一个顶点和当前顶点是否相连
            if (!visits[i] && grid[path[cur-1]][i] == 1) {
                visits[i] = true;
                path[cur] = i;
                //递归进入下一个顶点
                if (help(grid, target, visits, path, i+1)) return true;
                visits[i] = false;
                path[cur] = -1;
            }
        }
        return false;
}

数独问题

9*9数独不仅要求每行每列数字各不相同,而且要求每个小九宫格中的数字各不相同

    public boolean soduku(int[][] grid, int n) {
		boolean isEmpty = true;
		int x = grid.length;
		int y = grid[0].length;
		
		int row = -1, col = -1;
		for (int i = 0; i < x; i++) {
			for (int j = 0; j < y; j++) {
				if (grid[i][j] == 0) {
					isEmpty = false;
					row = i; col = j;
					break;
				}
			}
			if (!isEmpty)break;
		}
		if (isEmpty) return true;
		
		for (int i = 1; i <= n; i++) {
			if (isSafe(grid, row, col, i)) {
				grid[row][col] = i;
				if (soduku(grid, n)) return true;
				grid[row][col] = 0;
			}
		}
		return false;
	}
	public boolean isSafe(int[][] grid, int row, int col, int i) {
		for (int j = 0; j < grid.length; j++) {
			if (grid[j][col] == i) return false;
		}
		for (int j = 0; j < grid[0].length; j++) {
			if (grid[row][j] == i) return false;
		}
		int sqrt = (int) Math.sqrt(grid.length);
		int rowStart = row - row % sqrt;
		int colStart = col - col % sqrt;
		for (int x = rowStart; x < rowStart + sqrt; x++) {
			for (int y = colStart; y < colStart + sqrt; y++) {
				if (grid[x][y] == i) return false;
			}
		}
		return true;
	}

 拔河比赛

一群人要分成两队,每个人都有自己的体重,要求这两对人的体重的和相差最小,并且两队人要平均分配,最大允许误差为1,分别输出最后的两队人的体重和,小的在前

    /**
     * 拔河比赛
     *
     * @param nums 每个人的体重
     */
    public void tugWar(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        int len = nums.length;
        //dp[i][j]代表前i个人 在给定j体重范围内获得的最大体重和 可以先看这篇背包问题 https://blog.csdn.net/qq_38410730/article/details/81667885
        int[][] dp = new int[len +1][sum+1];
        for(int i = 0; i < len; i++) {
            dp[i][0] = 0;
        }
        for (int i = 0; i < sum;i++) {
            dp[0][i] = 0;
        }
        int m = len;
        //一半人
        if ((len & 1) == 0){
            m /= 2;
        }else {
            m = m/2+1;
        }
        //最大总体重的一半
        int n = sum / 2;

        for (int i = 0; i < len; i++) {
            for (int x = m; m > 0; m--) {
                for (int y = n; y >= nums[i]; y--) {
                    //前x人在给定y体重范围内的最大体重和 = max(前x人y体重范围内的最大体重和, 前x-1人去掉第x人剩余体重范围内的总体重和+第x人的体重)
                    dp[x][y] = Math.max(dp[x][y], dp[x-1][y-nums[i]]+nums[i]);
                }
            }
        }
        //题目要求输出时 小的在前
        int one = Math.min(dp[m][n], sum - dp[m][n]);
        int another = sum-one;
        System.out.println("一堆人的体重: " + one);
        System.out.println("另一堆人的体重: " + another);
    }

 

密码算法问题

A cryptarithmetic puzzle is a mathematical game where the digits of some numbers are represented by letters. Each letter represents a unique digit.

For example, a puzzle of the form:

  SEND
+ MORE
--------
 MONEY

may have the solution:

{'S': 9, 'E': 5, 'N': 6, 'D': 7, 'M': 1, 'O', 0, 'R': 8, 'Y': 2}

Given a three-word puzzle like the one above, create an algorithm that finds a solution.

对应解法: https://www.cnblogs.com/lz87/p/11306841.html

    private int[] resole = new int[26];

    public int[] playPuzzle(String word1, String word2, String word3) {
        Arrays.fill(resole, -1);
        boolean[] used = new boolean[10];
        if (word3.length() > word1.length() && word3.length() > word2.length()) {
            resole[word3.charAt(0) - 'A'] = 1;
            used[1] = true;
        }
        dfs(word1, word2, word3, word1.length() - 1, word2.length() - 1, word3.length() - 1, 0, used);
        return resole;
    }

    /**
     *
     * @param w1
     * @param w2
     * @param w3
     * @param i1   目前处理的是第几列
     * @param i2
     * @param i3
     * @param carry
     * @param used
     * @return
     */
    private boolean dfs(String w1, String w2, String w3, int i1, int i2, int i3, int carry, boolean[] used) {
        if (i3 < 0) {
            if (carry == 0) {
                return true;
            } else {
                return false;
            }
        }
        //如果i1还没处理完 而且当前字符还没有对应的数字
        if (i1 >= 0 && resole[w1.charAt(i1) - 'A'] < 0) {
            for (int d = 0; d <= 9; d++) {
                if (used[d]) {
                    continue;
                }
                //给w1的i1假设 对应的数字
                resole[w1.charAt(i1) - 'A'] = d;
                used[d] = true;
                if (dfs(w1, w2, w3, i1, i2, i3, carry, used)) {
                    return true;
                } else {
                    resole[w1.charAt(i1) - 'A'] = -1;
                    used[d] = false;
                }
            }
            return false;
        }

        //如果i2还没处理完 而且当前字符还没有对应的数字
        if (i2 >= 0 && resole[w2.charAt(i2) - 'A'] < 0) {
            for (int d = 0; d <= 9; d++) {
                if (used[d]) {
                    continue;
                }
                resole[w2.charAt(i2) - 'A'] = d;
                used[d] = true;
                if (dfs(w1, w2, w3, i1, i2, i3, carry, used)) {
                    return true;
                } else {
                    resole[w2.charAt(i2) - 'A'] = -1;
                    used[d] = false;
                }
            }
            return false;
        }
        //计算当前列的i1和i2对应数字的和
        int sum = carry;
        sum += i1 >= 0 ? resole[w1.charAt(i1) - 'A'] : 0;
        sum += i2 >= 0 ? resole[w2.charAt(i2) - 'A'] : 0;
        //如果word3当前列有对应的数字 而且和上面的和取模后的值相等
        if (resole[w3.charAt(i3) - 'A'] >= 0 && sum % 10 == resole[w3.charAt(i3) - 'A']) {
            //则进行下一列递归
            return dfs(w1, w2, w3, i1 - 1, i2 - 1, i3 - 1, sum / 10, used);
            //否则 如果word3当前列有对应的数字 但是与上面的和取模后的值不相等 或者上面的和取模后的值 已经被其他字母占用
        } else if (resole[w3.charAt(i3) - 'A'] >= 0 || used[sum % 10]) {
            return false;
        }
        //给word3的字母赋值对应的数字
        used[sum % 10] = true;
        resole[w3.charAt(i3) - 'A'] = sum % 10;
        //进行下一列递归
        if (dfs(w1, w2, w3, i1 - 1, i2 - 1, i3 - 1, sum / 10, used)) {
            return true;
        }
        //如果失败则回溯
        used[sum % 10] = false;
        resole[w3.charAt(i3) - 'A'] = -1;
        return false;
    }

穷举递归

给你一个字符串, 求字符串重排列后的所有可能

 

private void help(String have, String remain) {
        if (remain.length() == 0) {
            System.out.println(have);
            return;
        }
        for (int i = 0; i < remain.length(); i++) {
            help(have+remain.charAt(i), remain.substring(0,i) + remain.substring(i+1));
        }
}

 

矩阵最小路径和

给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后达到右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。如果给定的m如大小看到的样子,路径1,3,1,0,6,1,0是所有路径中路径和最小的,所以返回12。
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0

dp[i][j] 代表从[0,0]到[i,j]的最小路径和

public int pathSum(int[][] m){
    if(m==null || m.length==0 || m[0].length==0)
        return 0;

    int[][] dp = new int[m.length][m[0].length];
    dp[0][0] = m[0][0];
    for(int i=1;i<m.length;i++)
        dp[i][0] = m[i][0] + dp[i-1][0];
    for(int i=1;i<m[0].length;i++)
        dp[0][i] = m[0][i] + dp[0][i-1];

    for(int i=1;i<m.length;i++)
        for(int j=1;j<m[0].length;j++)
            dp[i][j] = m[i][j] + Math.min(dp[i][j-1], dp[i-1][j]);
        
    return dp[m.length-1][m[0].length-1];
}

最长递增子序列

给定数组arr,返回arr的最长递增子序列长度。比如arr=[2,1,5,3,6,4,8,9,7],最长递增子序列长度为[1,3,4,8,9],所以返回这个子序列的长度5。

dp[i] 代表前i个元素的最长递增子序列长度 dp[j]表示i的前一个元素的的最长递增子序列的长度

class Solution {
    public int lengthOfLIS(int[] nums) {
        int res = 0;
        int len = nums.length;
        if (len == 0) return res;
        int[] dp = new int[len];
        //1个元素最长递增子序列就是1
        Arrays.fill(dp, 1);
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    //dp[i] = 上个数的最长递增子序列长度+1
                    //因为最长递增子序列不一定连续 所以这里取最大值
                    //第二个dp[i]代表上一个dp[i]的值 然后在几个值中找到最大值
                    //通过选最大值, 避免了这种情况, 2,1,4,3,5,8,6,7 将2,4,5,8作为结果的错误判断
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            //最长子序列不一定在最后一个元素, 因此取最大值
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

字符串最长公共子序列

给定两个字符串str1和str2,返回两个字符串的最长公共子序列。例如:str1=“1A2C3D4B56”,str2=“B1D23CA45B6A”,"123456"或者"12C4B6"都是最长公共子序列,返回哪一个都行。

dp[i][j] 代表str1的[0,i]字符和str2的[0,j]字符的最长公共子序列

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        //i,j 代表长度 所以从1开始
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                //如果当前字符相等, 则dp[i] = dp[i-1]+1;
                if (text1.charAt(i-1) == text2.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else {
                    dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
                }
            }
        }
        return dp[m][n];
    }
}

0-1背包问题

一个背包有一定的承重W,有N件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。

dp[i][j]代表前i个物品 在j的容量下的最大价值

public int knapsack(int W, int[] w, int[] v){
    if(W<=0 || w==null || w.length==0 || v==null || v.length==0)
        return 0;
    int n = w.length;
    int[][] dp = new int[n+1][W+1];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=W;j++)
            if (j-w[i-1]>=0)
                //max(最后一个不装, 最后一个也装) 的 最大价值
                dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i-1]]+v[i-1]);
            else
                //最后一个不装
                dp[i][j] = dp[i-1][j];
    return dp[n][W];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值