算法基础
基础
与、或、异或
- 与&:同1为1 。 与1就是保留,与0就是清楚(屏蔽)
- 或|:有1为1 。
- 异或(不进位加法):同为0异为1 。 用来消去重复,异或0就是保留
- x&(x-1)就是将x二进制中最低位的一置为0
//统计二进制中1的个数
static int numberOf1(int x){
int count=0;
while (x!=0){
x&=x-1;
count++;
}
return count;
}
- x二进制中奇偶位互换
static int f(int i){
int ou=i&0xaaaaaaaa;//1010,偶数位置为0,异或0为清除
int ji=i&0x55555555;//0101,奇数位置为0
return (ji<<1)^(ou>>1);
}
- 用二进制表示的小数
static String decimalByBinary(double i){
//0.625表示成0.101(0.5+0.25)
// //整数转二进制除二,小数转二进制乘二
String s="0.";
while (i>0){
double num=i*2;
if (num>=1){
i=num-1;
s+="1";
}
else {
i=num;
s+="0";
}
if (s.length()>34)//小数点后32位不能表示的
return "ERROR";
}
return s;
}
简单的递归
/**
* 走39级台阶,每次走一步或者两步,有多少种走法
* @param n
* @param steps
* @return
*/
private static int f(int n, int steps) {
if (n<0)return 0;
if (n==0)return 1;
return f(n,steps-1)+f(n,steps-2);
}
/** 用递归实现插入排序
* 对前k-1个元素排序
* 把位置k的元素插入到前面的部分
* @param arr
* @param k 数组最后一个元素下标
* @return
*/
private static int[] insertSort(int[] arr, int k) {
if (k==0) return arr;
insertSort(arr,k-1);
int t=arr[k];
int index=k-1;
while (index>=0&&arr[index]>t){
arr[index+1]=arr[index];
index--;//1,2,6要插4: 6后移一位,1,2, ,6 同时index--,所以要在index+1位置上插入
}
arr[index+1]=t;//最后index
return arr;
}
用递归实现插入排序
/** 用递归实现插入排序
* 对前k-1个元素排序
* 把位置k的元素插入到前面的部分
* @param arr
* @param k 数组最后一个元素下标
* @return
*/
private static int[] insertSort(int[] arr, int k) {
if (k==0) return arr;
insertSort(arr,k-1);
int t=arr[k];
int index=k-1;
while (index>=0&&arr[index]>t){
arr[index+1]=arr[index];
index--;//1,2,6要插4: 6后移一位,1,2, ,6 同时index--,所以要在index+1位置上插入
}
arr[index+1]=t;//最后index
return arr;
}
打印汉诺塔问题路径
/**打印汉诺塔问题路径
*
* @param n 初始的N个从小到达的盘子,N是最大编号
* @param form
* @param to
* @param help
*/
private static void printHanoiTower(int n, String form, String to, String help) {
if (n==1){
System.out.println(" move "+n+" form "+form+" to "+to);
return;
}
printHanoiTower(n-1,form,help,to); //先把前N-1个盘子挪到辅助空间上去
System.out.println(" move "+n+" form "+form+" to "+to);//N可以顺利到达to
printHanoiTower(n-1,help,to,form); // 让N-1从辅助空间回到目标空间上去
}
查找与排序
用递归实现二分查找
private static int binarySearch1(int[]a, int form, int to, int key) {
if (form>to)
return -1;
int mid=(form+to)/2;
int midval=a[mid];
//也可以写成while(form<=to)而不用递归
if (midval<key)
return binarySearch1(a,mid+1,to,key);
else if (midval>key)
return binarySearch1(a,form,mid-1,key);
else
return mid;//写成三个if 编译会不通过
}
int[] a = {1, 6, 5, 9};
System.out.println(binarySearch1(a,0,3,5));
希尔排序
private static int[] shellSort(int[] a) {
for (int interval = a.length/2; interval >0 ; interval/=2) {//不断缩小增量
//1,6,5,9分两组,15一组,69一组,组内做插入排序
for (int i = interval; i <a.length ; i++) {
int target=a[i];//要插入的数
int index=i-interval;
while (index>=0&&a[index]>target){
a[index+interval]=a[index];
index-=interval;
}
a[index+interval]=target;
}
}
return a;
}
int[] a = {1, 6, 5, 9};
算法复杂度分析
- 斐波拉切递归的算法复杂度为2n
- gcd递归的时间复杂度为lg(n)
/**
* 时间复杂度约为lg(n)每次减半
* @param a
* @param n
* @return
*/
private static int pow(int a, int n) {
int res=a;
if (n==0)return 1;
if (n==1)return res;
int e=1;
while (e*2<=n){
res*=res;
e*=2;
}
return res*pow(a,n-e);
}
快速排序
package 算法基础;
/**
* @author 欧阳煜
* @date 2019/7/31 16:54
*/
public class 快排 {
public static void main(String[] args) {
int[]a={1,3,2,9,5};
for(int i:a)
System.out.print(i+" ");
quickSort(a,0,a.length-1);
System.out.println();
for(int i:a)
System.out.print(i+" ");
}
private static void quickSort(int[] a, int begin, int end) {
if (begin<end){
int midIndex=partition2(a,begin,end);
quickSort(a,begin,midIndex-1);
quickSort(a,midIndex+1,end);
}
}
private static int partition2(int[] a, int begin, int end) {
//双向扫描分区
int pivot=a[begin];
int left=begin+1;
int right=end;
while (left<right){
while (left<=right&&a[left]<=pivot)
left++;
while (left<=right&&a[right]>pivot)
right--;
if (left<right) {//当left不往前走即left指向了一个大的
int t = a[left];
a[left] = a[right];
a[right] = t;
}
}
int t=a[begin];
a[begin]=a[right];
a[right]=t;
return right;
}
private static int partition(int[] a, int begin, int end) {
//单向扫描分区
int pivot=a[begin];
int left=begin+1;
int right=end;
while (left<=right){
if (a[left]<=pivot)
left++;
else {
int t=a[left];
a[left]=a[right];
a[right]=t;
//扫描元素大于主元,交换左右指针的元素,右指针左移
right--;
}
}
int t=a[begin];
a[begin]=a[right];
a[right]=t;//把主元元素移到中间,下标为right
return right;
}
}
归并排序
package 算法基础;
/**
* @author 欧阳煜
* @date 2019/7/31 20:02
*/
public class 归并排序 {
public void mergeSort(int[]a,int begin,int end){
if(begin<end){
int midIndex=(begin+end)/2;
mergeSort(a,begin,midIndex-1);//左边排好序
mergeSort(a,midIndex+1,end);//右边排好序
merge(a,begin,midIndex,end);//合并两边的有序
}
}
public void merge(int []a,int begin,int midIndex,int end){
int []tmp=new int[a.length];//辅助数组
int p1=begin,p2=midIndex+1,k=begin;//p1、p2是检测指针,k是存放指针
while(p1<=midIndex && p2<=end){
if(a[p1]<=a[p2])
tmp[k++]=a[p1++];
else
tmp[k++]=a[p2++];
}
while(p1<=midIndex)
tmp[k++]=a[p1++];//如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
while(p2<=end)
tmp[k++]=a[p2++];//同上
//复制回原素组
for (int i = begin; i <=end; i++)
a[i]=tmp[i];
}
}
找数组第k小的元素
package 算法基础;
/**
* @author 欧阳煜
* @date 2019/8/1 8:49
*/
public class 找数组第k小的元素 {
//O(n)
public static int selectK (int[]a,int begin,int end,int k){
int pivotIndex=快排.partition2(a,begin,end);
int pivotIndexK=pivotIndex-begin+1;//主元在数组中是第几小的元素
if (pivotIndexK==k) return a[pivotIndex];//返回主元
else if (pivotIndexK>k)//是第6小 大于 要找的第4小
return selectK(a,begin,pivotIndex-1,k); //在左边找第4小
else //第2小 小于 要找的第4小
return selectK(a,pivotIndex+1,end,k-pivotIndexK);//在右边找第二小
}
public static void main(String[] args) {
int[]a={3,9,7,6,1,2};
System.out.println(selectK(a,0,a.length-1,4));
}
}
数组中出现次数超过半数的元素
package 算法基础;
/**
* @author 欧阳煜
* @date 2019/8/1 11:07
*/
public class 哪个数字超过了一半 {
public static void main(String[] args) {
System.out.println(moreThanHalf(new int[]{3,5,6,8,8,8,8}));
}
private static int moreThanHalf(int[] a) {
int candidate=a[0];
int times=1;//出现的次数
for (int i = 1; i < a.length; i++) {//扫描数组
if (times==0){
candidate=a[i];
times=1;
continue;
}
if(a[i]==candidate){
times++;//遇到和候选值相同的,次数加1
}
else
times--;//不同的数,进行消减
}
return candidate;
}
}
最小未出现的数字
package 算法基础;
/**
* @author 欧阳煜
* @date 2019/8/1 12:07
*/
public class 最小未出现的数字 {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5, 8, 9, 10, 11, 10000};
//6
System.out.println(find1(a,0,a.length-1));
System.out.println(find2(a));
}
/**
* 利用辅助空间
* @param a
* @return
*/
private static int find2(int[] a) {
int n=a.length;
int helper[]=new int[n+1];
for (int i = 0; i < n; i++) {
if (a[i]<n+1)
helper[a[i]]=1;
}
for (int i = 1; i < n+1; i++) {//下标为1,表示原数组中1这个元素有没有出现
if (helper[i] == 0) {
return i;
}
}
return n+1;//数组从1开始紧密,返回n+1
}
/***
* 利用递归
* @param a
* @return
*/
private static int find1(int[] a,int left,int right) {
if (left>right)
return left+1;
int midIndex=(left+right)/2;
int mid=找数组第k小的元素.selectK(a,left,right,midIndex-left+1);//实际在中间位置的值3
//1,2,4,6,7
int qi=midIndex+1;//期望值4
if (mid==qi)//左侧紧密
return find1(a,midIndex+1,right);
else //左侧稀疏
return find1(a,left,midIndex-1);
}
}
数组的包含问题
public class 数组的包含问题 {
public static void main(String[] args) {
System.out.println(contains("abc","bc"));
System.out.println("l".indexOf("f")>-1);
System.out.println(contains("abc","bc"));
System.out.println(contains1("abc","bc"));
}
/**
* 前n²,后n*lg(N)
* @param a
* @param b
* @return
*/
public static boolean contains(String a,String b){
for (int i = 0; i < b.length(); i++) {//a中找b,遍历b
if (a.indexOf(b.charAt(i))==-1){
return false;
}
}
return true;
}
public static boolean contains1(String a,String b){
char[]chars=a.toCharArray();
Arrays.sort(chars);
for (int i = 0; i < b.length(); i++) {
char t=b.charAt(i);
int index = Arrays.binarySearch(chars, t);
if (index==-1)
return false;
}
return true;
}
}
数学问题
扩展欧几里得算法求解线性方程
package 算法基础.数学问题;
/**
* @author 欧阳煜
* @date 2019/8/7 20:26
*/
public class 扩展欧几里得算法 {
/**
*
*/
static long x,y;
public static void main(String[] args) {
linerEquation(12,42,6);
//long d = extGcd(12, 42);
}
/**
* 调用完成后,xy是ax+by=gcd(a,b)的x大于0的第一个解
* @return
*/
public static long extGcd(long a,long b){
if (b==0){
x=1;
y=0;
return a;
}
long res=extGcd(b,a%b);
//x,y已经被下一层递归更新了,所以要备份
long x1=x;//备份x,
x=y;//更新x
y=x1-a/b*y;//更新y
return res;
}
/**
*
ax+by=m 当m=gcd(a,b)的倍数时有整数解
12x+42y=6,求出来解(-3,1),可推算出通解
x=x+b/d=-3+7=4 或减
y=y-a/d=1-2=-1
*/
public static void linerEquation(long a,long b ,long m) {
long gcd = extGcd(a, b);
if (m%gcd!=0) {System.out.println("No Answer");return;}
x*=m/gcd;
y*=m/gcd;
}
}
由分析可知:
x+mk与y+nk同余L(k表示相遇时跳的次数,L表示圈的长度)(余数相同叫同余)
可得(m-n)k+L*t=y-x
求最小的k
用拓展欧几里得x,再求通解里>0里最小的
常见求和公式
*1~n的平方和:n(n+1)(2n+1)/6 * 1~n的立方和:n²(n+1)/4 * 几何级数:x的0到n次方相加:(x.pow(n+1)-1)/(x-1) * 2的0到n次方相加:2.pow(n+1)-1 * if(abs(x)<1 :1/(1-x) * 调和级数1+1/2+1/3+...+1/n:约为ln(n) */ ```
同余方程中的逆元
/**
*
* ax%n=1,就说a关于n的逆元为x
* 即ax+ny=1
* 当gcd(a,n)=1才有解
* 特殊的,ax=1,就说a的逆元为x
*
*/
/*
已知a%9973和b,求(a/b)%9973
:
记b关于9973的逆元为x
化为(a*x)%9973
即(a%9973 * x)%9973
*/
public static long inverseElement(long a,long mo){
linerEquation(a,mo,1);
x=((x%mo)+mo)%mo;//保证x>0
return x;
}
质数分解
package 算法基础.数学问题;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class 质因数分解 {
public static void main(String[] args) {
List<Integer> list=primeFactor(100);
String s="";
for(Integer i:list){
s+="*"+i;
}
System.out.println(s.substring(1));
}
public static List<Integer> primeFactor(int num){
List<Integer> list=new ArrayList<>();
for (int i = 2; i *i<=num ; i++)
while (num%i==0){
list.add(i);
num/=i;
}
return list;
}
}
DFS
选若干个数,和为k
package 算法基础.dfs;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @author 欧阳煜
* @date 2019/8/27 14:10
*/
/**
* 给定整数序列a1,a2,...,an,判断是否可以从中选出若干数,使它们的和恰好为k.
*
* 1≤n≤20
*
* -10^8≤ai≤10^8
*
* -10^8≤k≤10^8
* 样例:
*
* 输入
*
* n=4
* a={1,2,4,7}
* k=13
* 输出:
*
* Yes (13 = 2 + 4 + 7)
*/
public class DFS_部分和 {
static int kk;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[]a=new int[n];
for (int i = 0; i <n; i++) {
a[i]=sc.nextInt();
}
int k=sc.nextInt();
kk=k;//最后k一定为0,先拷贝副本
f(a,k,0,new ArrayList<>());
}
private static void f(int[] a, int k, int cur, ArrayList<Integer> list) {
if (k==0){
System.out.print("YES("+kk+"=");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+(i==list.size()-1?"":"+"));
}
System.out.println(")");
}
if (k<0) return;
if (cur>a.length-1)return;
f(a,k,cur+1,list); //不要a[cur]这个元素
list.add(a[cur]);
int index=list.size()-1;//a[cur]在list中的下标
f(a,k-a[cur],cur+1,list); //要a[cur]这个元素,剩余k-a[cur]
list.remove(index);//回溯
}
}
n皇后问题
package 算法基础.dfs;
/**
* @author 欧阳煜
* @date 2019/8/27 16:41
*/
/**
* 请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个n*n的棋盘上放置n个皇后, 使得每行每列和每条对角线上都只有一个皇后(即皇后不能在同一行,不能在同一列,也不能在一条斜线上),求其摆放的方法数。
*/
public class n皇后问题_二维数组 {
public static int count;
public static void main(String[] args) {
int[][]a=new int[4][4];
dfs(a,0,0);
System.out.println(count);
}
private static void dfs(int[][] a, int x, int y) {
if (x==a.length){
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
System.out.print(a[i][j]+" ");
}
System.out.println();
}
count++;
return;
}
for (int i = 0; i < a.length; i++) { //选一个位置
if (check(a,x,i)){ //如果可以放
a[x][i]=1; //放
dfs(a,x+1,0);//下一行
a[x][i]=0; //回溯!!!
}
}
}
private static boolean check(int[][] a, int x, int y) {
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
if (i==x&&a[i][j]==1) return false;
if (j==y&&a[i][j]==1) return false;
if (x+y==i+j&&a[i][j]==1) return false;
if (y-x==j-i&&a[i][j]==1) return false;
}
}
return true;
}
}
困难的串
package 算法基础.dfs;
/**
* @author 欧阳煜
* @date 2019/8/27 19:28
*/
/**
* 问题描述:如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串,
* 如:BB,ABCDACABCAB,ABCDABCD都是容易的,A,AB,ABA,D,DC,ABDAB,CBABCBA都是困难的。
输入正整数n,L,输出由前L个字符(大写英文字母)组成的,字典序第n小的困难的串。
例如,当L=3时,前7个困难的串分别为:
A,AB,ABA,ABAC,ABACA,ABACAB,ABACABA
n指定为4的话,输出ABAC
*/
public class 困难的串 {
public static int count;
public static void main(String[] args) {
int n=4;
int l=3;
dfs(l,n,"");
}
private static void dfs(int l, int n, String prefix) {
if (count==4){
System.out.println(prefix);
System.exit(0);
}
for (char i ='A'; i <'A'+l ; i++) {//尝试在prefix后追加一个字符
// if (isHard(prefix,i)){
if (isHard(prefix+i)){
System.out.println(prefix+i);
count++;
dfs(l,n,prefix+i);
}
}
}
private static boolean isHard(String s) {
s=new String( new StringBuilder(s).reverse());
for (int i = 1; i <= s.length()/2; i++) {//写for循坏千万要注意等不等于的问题
String s1=s.substring(0,i);
String s2=s.substring(i,2*i);
if (s1.equals(s2))
return false;
}
return true;
}
}
数独
package 算法基础.dfs;
/**
* @author 欧阳煜
* @date 2019/8/27 12:57
*/
import java.util.Scanner;
/**
* 你一定听说过“数独”游戏。 如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。 数独的答案都是唯一的,所以,多个解也称为无解。 本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。 本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。 格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。 输出9行,每行9个数字表示数独的解。
*/
public class 数独求解_DFS {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char[][] a = new char[9][9];
for (int i = 0; i < 9; i++) {
a[i] = sc.nextLine().toCharArray();
}
dfs(a, 0, 0);
}
private static void dfs(char[][] a, int x, int y) {
if (x == 8 && y == 8) {//退出的条件
for (int i = 0; i < 9; i++) {
System.out.println(new String(a[i]));
}
System.exit(0);//不用return,return还会找其他路,题意有唯一解,找到就退走程序
}
if (a[x][y] == '0') {//如果要填
for (int i = 1; i < 10; i++) { //选一个
if (check(a, x, y, i)) { //如果可以填
a[x][y] = (char) ('0' + i); //填上
dfs(a, x + (y + 1) / 9, (y + 1) % 9); //下一位
}
}
a[x][y] = '0'; //回溯
} else //已经有了数字
dfs(a, x + (y + 1) / 9, (y + 1) % 9); //下一位
}
private static boolean check(char[][] a, int x, int y, int k) {
for (int i = 0; i < 9; i++) {
if (a[i][y] == (char) ('0' + k))
return false;
if (a[x][i] == (char) ('0' + k))
return false;
for (int j = x / 3 * 3; j < (x/ 3+ 1) * 3; j++) { // 所在小九宫格起始行
for (int l = y / 3 * 3; l < (y/ 3+ 1) * 3; l++) {// 所在小九宫格起始列
if (a[j][l]==(char) ('0' + k))
return false;
}
}
}
return true;
}
}
最大公共子序列
package 算法基础.dfs;
import java.util.ArrayList;
/**
* @author 欧阳煜
* @date 2019/8/31 9:54
*/
public class 最大公共子序列 {
public static void main(String[] args) {
System.out.println(dfs("3563243", "513141"));
}
static ArrayList<Character> dfs(String s1, String s2) {
int len1 = s1.length();
int len2 = s2.length();
ArrayList<Character> ans = new ArrayList<>();
for (int i = 0; i < len1; i++) {
//求以i字符开头的公共子序列
ArrayList<Character> list = new ArrayList<>();
//和s2的每个字符比较
for (int j = 0; j < len2; j++) {
if (s1.charAt(i) == s2.charAt(j)) {//如果相同
list.add(s1.charAt(i));
list.addAll(dfs(s1.substring(i + 1), s2.substring(j + 1)));
break;
}
}
if (list.size() > ans.size()) {
ans = list;
}
}
return ans;
}
}
素数环
package 算法基础.dfs;
/**
* @author 欧阳煜
* @date 2019/8/27 18:55
*/
/**
* 输入正整数n,对1-n进行排列,使得相邻两个数之和均为素数,
* 输出时从整数1开始,逆时针排列。同一个环应恰好输出一次。
* n<=16
*
* 如输入:6
* 输出:
* 1 4 3 2 5 6
* 1 6 5 2 3 4
*/
public class 素数环 {
public static void main(String[] args) {
int n=6;
int []a=new int[6];
a[0]=1;
dfs(a,1);
}
private static void dfs(int[] a, int cur) {
if (cur==a.length&&isP(a[0]+a[a.length-1])){//填到末尾了,还有首尾相加为素数才算成功
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
System.out.println();
return;
}
for (int i = 2; i <=a.length; i++) {
if (check(a,i,cur)){//a中没有i这个数,且和上一个数之和为素数
a[cur]=i;
dfs(a,cur+1);
a[cur]=0;//回溯
}
}
}
private static boolean check(int[] a, int i, int cur) {
for (int e:a)
if (e==i||!isP(a[cur-1]+i))
return false;
return true;
}
private static boolean isP(int i) {
for (int j = 2; j <=Math.sqrt(i) ; j++) {
if (i%j==0)return false;
}
return true;
}
}
贪心
不相交区间
package 算法基础.贪心;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author 欧阳煜
* @date 2019/8/29 17:36
*/
/*
有n项工作,每项工作分别在si时间开始,在ti时间结束.
对于每项工作,你都可以选择参与与否.如果选择了参与,那么自始至终都必须全程参与.
此外,参与工作的时间段不能重复(即使是开始的瞬间和结束的瞬间的重叠也是不允许的).
你的目标是参与尽可能多的工作,那么最多能参与多少项工作呢?
1≤n≤100000
1≤si≤ti≤10^9
输入:
第一行:n
第二行:n个整数空格隔开,代表n个工作的开始时间
第三行:n个整数空格隔开,代表n个工作的结束时间
样例输入:
5
1 3 1 6 8
3 5 2 9 10
样例输出:
3
说明:选取工作1,3,5
*/
public class 不相交区间 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[]s=new int[n];
int[]e=new int[n];
for (int i = 0; i <n; i++) {
s[i]=sc.nextInt();
}
for (int i = 0; i <n; i++) {
e[i]=sc.nextInt();
}
Job[] jobs=new Job[n];
for (int i = 0; i < n; i++) {
jobs[i]=new Job(s[i],e[i]);
}
Arrays.sort(jobs);
System.out.println(f(n,jobs));
}
private static int f(int n, Job[] jobs) {
int count=1;
int y=jobs[0].e;//总是选择最先完成的
for (int i = 0; i < n; i++) {
if (jobs[i].s > y) {//保证不相交
count++;
y=jobs[i].e;//更新最后完成时间
}
}
return count;
}
/**
* 内部类
* 便于将e[]排序时s[]也跟着变
*
*/
public static class Job implements Comparable<Job> {
int s;
int e;
public Job(int s, int e) {
this.s = s;
this.e = e;
}
@Override
public int compareTo(Job o) {
if (this.e==o.e)
return this.s-o.s;
return this.e-o.e;
}
}
}
字典序最小
package 算法基础.贪心;
import java.util.Scanner;
/**
* @author 欧阳煜
* @date 2019/8/29 18:55
*/
/*
字典序最小问题
给一个定长为N的字符串S,构造一个字符串T,长度也为N。
起初,T是一个空串,随后反复进行下列任意操作
1. 从S的头部删除一个字符,加到T的尾部
2. 从S的尾部删除一个字符,加到T的尾部
目标是最后生成的字符串T的字典序尽可能小
1≤N≤2000
字符串S只包含大写英文字母
输入:字符串S
acdbcb
输出:字符串T
abcbcd
POJ - 3617 要求每80个字符换行输出
*/
public class 字典序最小 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// String s = sc.nextLine();
formatPrint(f(sc.nextLine()));
System.out.println();
}
private static String f(String s) {
System.out.println(s.length());
String ss=new StringBuilder(s).reverse().toString();
StringBuilder res=new StringBuilder();
while (res.length()<=s.length()+1){
if (s.compareTo(ss)<=0){
res.append(s.charAt(0));
s=s.substring(1);
}
else {
res.append(ss.charAt(0));
ss=ss.substring(1);
}
}
return res.toString();
}
public static void formatPrint(String s){
for (int i = 0; i < s.length(); i++) {
System.out.print(s.charAt(i));
if ((i+1)%80==0) {
System.out.println();
}
}
}
}
快速过桥
package 算法基础.贪心;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author 欧阳煜
* @date 2019/8/29 9:30
*/
public class 快速过桥 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int[]speed=new int[n];
for (int i = 0; i <n; i++) {
speed[i]=sc.nextInt();
}
Arrays.sort(speed);
System.out.println(f(n,speed));
}
private static int f(int n, int[] speed) {
int ans=0;
while (n>0){
if (n==1){
ans+=speed[0];
break;
}
if (n==2){
ans+=speed[1];
break;
}
if (n==3){
ans+=speed[0]+speed[1]+speed[2];
break;
}
if (n>3){
int s1=speed[0]+speed[1]*2+speed[n-1];//a+3b+d
int s2=2*speed[0]+speed[n-1]+speed[n-2];//2a+b+c+d
ans+=Math.min(s1,s2);
n-=2;
}
}
return ans;
}
}
最少的硬币数
package 算法基础.贪心;
import java.util.Scanner;
import static java.lang.Math.min;
/**
* @author 欧阳煜
* @date 2019/8/28 19:37
*/
/*
硬币问题
有1元,5元,10元,50元,100元,500元的硬币各c1,c5,c10,c50,c100,c500枚.
现在要用这些硬币来支付A元,最少需要多少枚硬币?
假定本题至少存在一种支付方案.
0≤ci≤10^9
0≤A≤10^9
输入:
第一行有六个数字,分别代表从小到大6种面值的硬币的个数
第二行为A,代表需支付的A元
样例:
输入
3 2 1 3 0 2
620
输出
6
*/
public class 最少的硬币数 {
static int[] coins = {1, 5, 10, 50, 100, 500};
static int[] cnts = new int[6];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
for (int i = 0; i < 6; i++) {
cnts[i] = sc.nextInt();
}
int n = sc.nextInt();
int res = ff(n, 5);//从面值最大的开始算
System.out.println(res);
}
private static int ff(int n, int cur) {
if (n<=0)return 0;
if (cur==0)return n;//最小面值为1
int value=coins[cur];//600/5
int x=n/value;
int num=cnts[cur];//有的数目
int min=min(x,num);
return min+ff(n-min*value,cur-1);
}
}
最少的船坐最多的人
package 算法基础.贪心;
/**
* @author 欧阳煜
* @date 2019/8/30 9:55
*/
import java.util.Arrays;
/**
有n个人,第i个人重量为wi。每艘船的最大载重量均为C,且最多只能乘两个人。用最少的船装载所有人。
贪心策略:考虑最轻的人i,如果每个人都无法和他一起坐船(重量和超过C),则唯一的方案是每个人坐一艘
否则,他应该选择能和他一起坐船的人中最重的一个j
求需要船的数量
*/
public class 最少的船坐最多的人 {
public static void main(String[] args) {
int[] weight = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = weight.length;
int c = 10;
Arrays.sort(weight);
int numOfPerson=n;
int boats=0;
int p1=0;
int p2=n-1;//首尾指针
while (numOfPerson>0){
if (weight[p1]+weight[p2]>c){
p2--;
numOfPerson--;
boats++;
}
else {
p1++;
p2--;
numOfPerson-=2;
boats++;
}
}
System.out.println(boats);
}
}
部分背包问题
package 算法基础.贪心;
/**
* @author 欧阳煜
* @date 2019/8/30 8:37
*/
import java.util.Arrays;
/**
有n个物体,第i个物体的重量为wi,价值为vi。在总重量不超过C的情况下让总价值尽量高。
每一个物体都可以只取走一部分,价值和重量按比例计算。
求最大总价值
注意:每个物体可以只拿一部分,因此一定可以让总重量恰好为C。
*/
public class 部分背包问题 {
public static void main(String[] args) {
int []weight={1,2,3,4,5};
int []value={3,4,3,1,4};
int n=5;
double c=10;
Goods[]goods=new Goods[n];
for (int i = 0; i < n; i++) {
goods[i]=new Goods(weight[i],value[i]);
}
Arrays.sort(goods);
double maxValue=0;
for (int i = n-1; i >= 0; i--) { //从单价最大的开始拿
if (goods[i].weight<=c){ //背包容量够
maxValue+=goods[i].value;
c-=goods[i].weight;
}
else { //背包容量不够拿完
maxValue+=goods[i].value*(c/goods[i].weight);
break;
}
}
System.out.println(maxValue);
}
public static class Goods implements Comparable<Goods>{
int weight;
int value;
public Goods(int weight, int value) {
this.weight = weight;
this.value = value;
}
public double getPrice(){
return value/(double) weight;
}
@Override
public int compareTo(Goods o) {
if (this.getPrice()==o.getPrice()) return 0;
if (this.getPrice()<o.getPrice()) return -1;
return 1;
}
}
}
动态规划
01背包问题
package 算法基础.动态规划;
import java.util.Arrays;
import static java.lang.Math.max;
/**
* @author 欧阳煜
* @date 2019/8/30 10:44
*/
/*
有n个重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
1≤n≤100
1≤wi,vi≤100
1≤W≤10000
输入:
n=4
(w,v)={(2,3),(1,2),(3,4),(2,2)}
W=5
输出:
7(选择第0,1,3号物品)
因为对每个物品只有选和不选两种情况,所以这个问题称为01背包。
*/
public class _01背包问题 {
static int[] weight = {2, 1, 3, 2};//重量表
static int[] value = {3, 2, 4, 2};//价值表
static int n = 4;//物品数量
static int w = 5;//背包的承重极限
// static int[] weight = {1,2,3,4,5,6,7,8,9,10};//重量表
// static int[] value = {1,5,8,16,10,17,17,20,24,30};//价值表
// static int n = 10;//物品数量
// static int w = 10;//背包的承重极限
static int [][]rec=new int[n][w+1];//对于每一对n,w,保存一个结果
public static void main(String[] args) {
for (int i = 0; i < n; i++) {
Arrays.fill(rec[i],-1);//因为结果可能存0
}
System.out.println(dfs(0,w));
System.out.println(dp());
}
/**
* 记忆性递归
* @param i
* @param w
* @return
*/
private static int dfs(int i, int w) {
if (w<=0)return 0;//装不进去
if (i==n)return 0;//没东西可选了
if (rec[i][w]>=0) //1.计算之前先查询rec
return rec[i][w];
int ans;
int buxuan=dfs(i+1,w);//不选择当前物品
if (w>=weight[i]){ //一点要注意能否取等
int xuan=value[i]+dfs(i+1,w-weight[i]); //选择当前物品
ans= max(xuan,buxuan);
}else
ans=buxuan ;//容量不够,不能选
//2.计算之后保存到rec
rec[i][w]=ans;
return ans;
}
/**
* 动态规划
* @return
*/
public static int dp(){
int[][]dp=new int[n][w+1];
//初始化dp表的第一行
for (int i = 0; i < w + 1; i++) {
if (i>=weight[0]){
dp[0][i]=value[0];
}
else dp[0][i]=0;
}
// 其他行
for (int i = 1; i < n; i++) {
for (int j = 0; j < w + 1; j++) {
if (j>=weight[i]){ //包的容量够
int xuan=value[i]+dp[i-1][j-weight[i]];//选择当前物品即i号物品,剩余容量
//j-weight[i] 剩余容量
//i-1 上一行
//value[i] 选的物品价值
int buxuan=dp[i-1][j];
dp[i][j]= max(xuan,buxuan);
}
else
dp[i][j]=dp[i-1][j];
}
}
return dp[n-1][w];//最后下角的格子
}
}
完全背包
package 算法基础.动态规划;
import static java.lang.Math.max;
/**
* @author 欧阳煜
* @date 2019/8/31 10:20
*/
/**
* 首先在初始化最后一行的时候有所不同
*
* 要抓的时候,先抓到这个物品的价值,然后用剩下的容量去匹配同一行
*
*/
public class 完全背包 {
public static void main(String[] args) {
System.out.println(dp());
}
static int[] weight = {7,4,3,2};//重量表
static int[] value = {9,5,3,1};//价值表
static int n = 4;//物品数量
static int w = 10;//背包的承重极限
public static int dp(){
int[][]dp=new int[n][w+1];
//初始化dp表的第一行
for (int i = 0; i < w + 1; i++)
dp[0][i]=i/weight[0]*value[0];
// 其他行
for (int i = 1; i < n; i++) {
for (int j = 0; j < w + 1; j++) {
if (j>=weight[i]){ //包的容量够
int xuan=value[i]+dp[i][j-weight[i]];//选择当前物品即i号物品,剩余容量
int buxuan=dp[i-1][j];
dp[i][j]= max(xuan,buxuan);
}
else
dp[i][j]=dp[i-1][j];
}
}
return dp[n-1][w];//最后下角的格子
}
}
数字三角形
package 算法基础.动态规划;
/**
* @author 欧阳煜
* @date 2019/8/31 8:32
*/
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Stack;
/**
* 数字三角形(POJ1163)<br>
*
* 在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。<br>
* 路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。<br>
* 三角形的行数大于1小于等于100,数字为 0 - 99<br>
* 输入格式:<br>
* 5 //表示三角形的行数 接下来输入三角形<br>
* 7
* 3 8
* 8 1 0
* 2 7 4 4
* 4 5 2 6 5
* 要求输出最大和
*
*
*/
public class 数字三角形 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] triangle = new int[n][];
for (int i = 0; i < n; i++) {
triangle[i] = new int[i + 1];
for (int j = 0; j < i + 1; j++) {
triangle[i][j] = sc.nextInt();
}
}
System.out.println(dp(triangle, 0, 0));
System.out.println(dfs(triangle,0,0));
}
public static int[][]rec;
private static int dfs( int[][] triangle, int i, int j) {
if (i==triangle.length-1)
return triangle[i][j]; //最后一行的话
else
if (rec[i][j]>0)
return rec[i][j];
else
rec[i][j]=triangle[i][j]+Math.max(dfs(triangle,i+1,j),dfs(triangle,i+1,j+1));
return rec[i][j];
}
private static int dfss( int[][] triangle, int i, int j) {
if (i==triangle.length-1)
return triangle[i][j]; //最后一行的话
else
return triangle[i][j]+Math.max(dfss(triangle,i+1,j),dfs(triangle,i+1,j+1));
}
//可以用滚动数组,因为只需要依赖上一行,把上一行的数据不断覆盖就行
private static int dp(int[][] triangle, int i, int j) {
int row = triangle.length;
int column = triangle[row - 1].length;//最后一行列数
int[][]dp=new int[row][column];
for (int k = 0; k < column; k++) {
dp[row-1][k]=triangle[row-1][k];
}
for (int k = row-2; k >=0 ; k--) {
for (int l = 0; l <=k; l++) {
dp[k][l]=triangle[k][l]+Math.max(dp[k+1][l],dp[k+1][l+1]);
}
}
return dp[0][0];
}
}
最长递增子序列
package 算法基础.动态规划;
/**
* @author 欧阳煜
* @date 2019/8/31 13:00
*/
/**
* 最长递增子序列的长度
输入 4 2 3 1 5 6
输出 3 (因为 2 3 5组成了最长递增子序列)
*/
public class 最长递增子序列 {
static int[] a={4,2,3,1,5,6};
public static void main(String[] args) {
System.out.println(f(a));
}
/**
* By Force
* @param a
* @return
*/
private static int f(int[] a) {
int maxCnt=0;
for (int i = 0; i < a.length; i++) { //对于每个元素都有一个cnt
int p=i; //当前最大元素下标
int cnt=1;
for (int j = i+1; j <a.length ; j++) {
if (a[j]>a[p]){
cnt++;
p=j;
}
}
maxCnt=Math.max(maxCnt,cnt);
}
return maxCnt;
}
}
钢条切割
package 算法基础.动态规划;
import java.util.Arrays;
/**
* @author 欧阳煜
* @date 2019/8/30 16:42
*/
/*
Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。
| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| - | - | - | - | - | - | - | - | - | - |
价格pi | 1 | 5 | 8 | 16 | 10 | 17 | 17 | 20 | 24 | 30 |
钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。
注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。
*/
public class 钢条切割 {
//跟01背包不同这个可以多次选择
static int n=10;
static int []p={1, 5, 8, 16, 10, 17, 17, 20, 24, 30};//p[0]表示长度为1的收益
static int[]rec=new int[n+1];//保存下标为n的钢条最大收益
public static void main(String[] args) {
Arrays.fill(rec,-1);
System.out.println(f(10));
Arrays.fill(rec,-1);
System.out.println(dp(10));
}
/**
* dp
* @param length
* @return
*/
private static int dp(int length) {
rec[0]=0;
for (int i = 1; i <= n; i++) {//拥有的钢条长度
for (int j = 1; j <= i; j++) {//保留j为整段
rec[i]=Math.max(rec[i],p[j-1]+rec[i-j]);
}
}
return rec[length];
}
/**
* 记忆性递归
* @param length
* @return
*/
public static int dfs(int length){
if (length==0) return 0;
int ans=0;
for (int i = 1; i <= length; i++) { //尝试不同长度分割
if (rec[length-i]==-1)
rec[length-i]=dfs(length-i);
int profit=p[i-1]+rec[length-i];
ans=Math.max(ans,profit);
}
rec[length]=ans;
return ans;
}
/**
* normal
* @param x
* @return
*/
public static int f(int x){
if (x==0) return 0;
int ans=0;
for (int i = 1; i <= x; i++) { //尝试不同长度分割
int profit=p[i-1]+f(x-i);
ans=Math.max(ans,profit);
}
return ans;
}
}