数据结构与算法(java)–链表
数据结构与算法(Java)–栈和递归
数据结构与算法(java)–排序算法及查找
数据结构与算法(java)–哈希表
数据结构与算法(Java)–数结构
数据结构与算法(Java)–图结构
数据结构与算法(Java)–常见算法
leetcode hot100
分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……
分治法在每一层递归上都有三个步骤:
A、B、C三个塔
如果只有一个盘,直接A->C
如果大于等于两个盘,就分成两部分。
最下面的一个盘为一部分,上面的所有盘为一部分
public class Demo1 {
public static void main(String[] args) {
hanoiTower(3, 'A', 'B', 'C');
}
/**
* 汉诺塔
*
* @param num 盘的总数
* @param a 第一个塔
* @param b 第二个塔
* @param c 第三个塔
*/
public static void hanoiTower(int num, char a, char b, char c) {
//如果只有一个盘,把这个盘从A移动到C
if(num == 1) {
System.out.println("把第" + num + "个盘从" + a + "->" + c);
return;
}
//如果大于等于两个盘,将上面部分的盘从A借助C移动到B
hanoiTower(num-1, a, c, b);
//把最下面的盘从A移动到C
System.out.println("把第" + num + "个盘从" + a + "->" + c);
//把上面部分的盘从B借助A移动到C
hanoiTower(num-1, b, a, c);
}
}
运行结果
把第1个盘从A->C
把第2个盘从A->B
把第1个盘从C->B
把第3个盘从A->C
把第1个盘从B->A
把第2个盘从B->C
把第1个盘从A->C
在刷leetcode时有幸看到了一位大佬写的关于递归的博客,在此转载贴出。
点此跳转
物品 | 重量 | 价值 |
---|---|---|
吉他 | 1 | 1500 |
音响 | 4 | 3000 |
电脑 | 3 | 2000 |
一个背包最多装4kg的东西,求
算法的主要思想,利用动态规划来解决。每次遍历到的第 i个物品,根据 w[i]和 v[i]来确定是否需要将该物品放入背包中。即对于给定的 n个物品,设 v[i]、w[i]分别为第 i个物品的价值和重量,C为背包的容量。再令二维数组
v[i][j]
表示在前 i个物品中能够装入容量为 j的背包中的最大价值。则我们有下面的结果
//表示填入表的第一行和第一列是 0,主要是为了方便表示物品和容量
(1) v[i][0]=v[0][j]=0;
// 当准备加入新增的商品的重量大于当前背包的容量时,就直接使用上一个单元格的装入策略(装入物品的价值)
(2) 当 w[i]>j 时:v[i][j]=v[i-1][j]
// 当准备加入的新增的商品的容量小于等于当前背包的容量,
// 装入的方式:
(3) 当 j>=w[i]时:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
v[i-1][j]:上一个装法的总价值
v[i] : 表示当前商品的价值
v[i-1][j-w[i]] : 装入i-1商品,到剩余空间j-w[i]的总价值
简单来说:
装入物品的容量大于背包容量时,直接使用之前装入背包物品的最大价值
装入物品容量小于等于背包容量时,比较
选取较大者
public class Demo2 {
public static void main(String[] args) {
//各个物品的重量
int[] weight = {1, 4, 3};
//各个物品的价值
int[] value = {1500, 3000, 2000};
//背包的最大容量
int maxSize = 4;
//各种方法的价值的最大值,第0行和第0列值为0,方便后续操作
int[][] maxValue = new int[value.length+1][maxSize+1];
//用于表示物品放入背包的方式
int[][] method = new int[value.length+1][maxSize+1];
//依次将物品放入背包
for(int i = 1; i j) {
maxValue[i][j] = maxValue[i-1][j];
} else {
//背包剩余的容量
int remaining = j - weight[i-1];
//如果放入该物品前的最大价值大于放入该物品后的最大价值,就不放入该物品
if(maxValue[i-1][j] > value[i-1]+maxValue[i-1][remaining]) {
maxValue[i][j] = maxValue[i-1][j];
} else {
maxValue[i][j] = value[i-1]+maxValue[i-1][remaining];
//存入放入方法
method[i][j] = 1;
}
}
}
}
//打印放入背包的最大价值
for(int[] arr : maxValue) {
System.out.println(Arrays.toString(arr));
}
//打印价值最大的放法
//存放方法的二维数组的最大下标,从最后开始搜索存放方法
int i = method.length - 1;
int j = method[0].length - 1;
while(i > 0 && j > 0) {
if(method[i][j] == 1) {
System.out.println("将第" + i + "个物品放入背包");
//背包剩余容量
j -= weight[i-1];
}
i--;
}
}
}
运行结果
[0, 0, 0, 0, 0]
[0, 1500, 1500, 1500, 1500]
[0, 1500, 1500, 1500, 3000]
[0, 1500, 1500, 2000, 3500]
将第3个物品放入背包
将第1个物品放入背包
KMP是一个解决模式串在文本串是否出现过,如果出现过,找出最早出现的位置的经典算法
**问题:**有一个字符串 str1= BBC ABCDAB ABCDABCDABDE,和一个子串 str2=ABCDABD。现在要判断 str1 是否含有 str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
重要步骤
这时候,想到的是继续遍历 str1的下一个字符,重复第 1步。(其实是很不明智的,因为此时 BCD已经比较过了,没有必要再做重复的工作,一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是”ABCDAB”
怎么做到把刚刚重复的步骤省略掉?可以对 str2计算出一张部分匹配表,这张表的产生在后面介绍
str2的部分匹配表如下
搜索词 | A | B | C | D | A | B | D |
---|---|---|---|---|---|---|---|
部分匹配值 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
已知空格与 D不匹配时,前面六个字符”ABCDAB”是匹配的。查表可知,最后一个匹配字符B对应的部分匹配值为 2,因此按照下面的公式算出向后移动的位数:
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为 2(”AB”),对应的部分匹配值为0。
所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移 2 位
前缀与后缀
部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例,
public class Demo3 {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int result = getPosition(str1, str2);
if(result != -1) {
System.out.print("匹配位置是:str1[");
System.out.println(result + "]");
} else {
System.out.println("匹配失败");
}
}
/**
*得到匹配字符串的部分匹配表
*
* @param matchStr 用于匹配的字符串
* @return 部分匹配表
*/
public static int[] getTable(String matchStr) {
//部分匹配值的数组
int[] sectionTable = new int[matchStr.length()];
//匹配字符串的第一个元素没有前缀与后缀,部分匹配值为0
sectionTable[0] = 0;
//i用来指向部分匹配字符串末尾的字符,j用来指向开始的字符
for(int i = 1, j = 0; i0且前缀后缀不匹配时,使用部分匹配表中前一个表项的值
while (j > 0 && matchStr.charAt(j) != matchStr.charAt(i)) {
j = sectionTable[j-1];
}
//如果前缀后缀匹配,j向后移,继续比较
if(matchStr.charAt(j) == matchStr.charAt(i)) {
j++;
}
//存入匹配值
sectionTable[i] = j;
}
return sectionTable;
}
/**
* 通过KMP算法匹配字符串,若匹配成功,返回第一个字符出现的位置
*
* @param str1 用于匹配的字符串
* @param str2 要匹配的字符串
* @return 第一个字符出现的位置,没有则返回-1
*/
public static int getPosition(String str1, String str2) {
//获得str2的部分匹配表
int[] sectionTable = getTable(str2);
for(int i = 0, j = 0; i < str1.length(); i++) {
//两个字符匹配
if(str1.charAt(i) == str2.charAt(j)) {
j++;
if(j == str2.length()) {
//如果匹配完成,返回第一个字符出现位置
return i - str2.length() + 1;
}
} else {
//如果匹配失败了,使用部分匹配表,跳转到str1对应位置
//如果j==0,说明没有字符被被匹配,直接让i指向str1的下一个字符
if(j == 0) {
continue;
}
//跳转步数 = 已经匹配的字符个数 - 部分匹配表对应的值
int position = j - sectionTable[j-1];
i += position;
//因为循环后会+1,所以此处i-1
i--;
//重置j,重新匹配
j = 0;
}
}
return -1;
}
}
运行结果
匹配位置是:str1[15]
希望能够导致结果是最好或者最优的算法
假设存在下面需要付费的广播台,以及广播台信号可以覆盖的地区。如何选择最少的广播台,让所有的地区都可以接收到信号
电台 | 覆盖地区个数 | 覆盖地区 |
---|---|---|
K1 | 0 | 北京 上海 天津 |
K2 | 0 | 广州 北京 深圳 |
K3 | 0 | 成都 上海 杭州 |
K4 | 0 | 上海 天津 |
K5 | 0 | 杭州 大连 |
思路
遍历,发现K3覆盖的地区最多,将K3覆盖的地区从地区集合中移除。然后将K3放入电台集合中,并更新覆盖地区个数
假设纸币金额为1元、5元、10元、20元、50元、100元
要凑成123元应该尽可能兑换少的纸币
public class Demo1 {
public static void main(String[] args) {
splitChange(123);
}
/**
* 拆分零钱
*
* @param money 钱币总金额
*/
public static void splitChange(int money) {
//零钱金额,纸币的种类
int[] prices = {100, 50, 20, 10, 5, 1};
//用于记录每种纸币的数量,下标与prices数组的下标对应
int[] counts = new int[prices.length];
//剩下的金额
int surplus = money;
if(money > 0) {
//如果剩下的金额大于0
while(surplus > 0) {
//从大金额向小金额进行凑数
for(int i = 0; i= 0) {
count++;
surplus -= prices[i];
}
counts[i] = count;
}
}
}
//打印结果
System.out.println("凑成" + money +"元");
for(int i = 0; i
运行结果
凑成123元
需要100元的纸币1张
需要20元的纸币1张
需要1元的纸币3张