算法随笔
1.输入接受一个数字 n ,求一个队列,该队列由数字[1...n]组成, 且满足不断将队头元素移到队尾,然后输出队头,如此循环,最后的输出是有序的。
思路:用有序的队列存放下标 [0...n-1] ,用下标进行移到队尾,出队的操作,出队的值对应的是 [1....n] ,这就构建了一个下标到数字的映射 map,存放到一个长度为 n 的数组即可。
public static void getQueue(int n){ int[] map = new int[n]; LinkedList<Integer> queue = new LinkedList<Integer>(); for(int i = 0 ; i < n; i ++) queue.add(i); int pos = 1; while(!queue.isEmpty()){ queue.addLast(queue.removeFirst()); map[queue.removeFirst()] = pos ++; } for(int i = 0 ; i < map.length ; i ++) {System.out.print(map[i] +" ");} System.out.println(); }
2. LRU cache leetcode
思路:hashmap + 双链表 ,但是 TLE 了==!
private Map<Integer,Integer> map; private int capacity; // 容量 private ListNode fake ; private ListNode tail ; public LRUCache(int capacity) { fake = new ListNode(-1); //虚拟头结点 tail = fake; // 尾部节点,便于删除 map = new HashMap<Integer,Integer>(capacity); this.capacity = capacity; } //将 key 对应的节点,移到最前边 public void rm2front(int key){ ListNode cur = fake.next; //找到 key 对应的节点 while(cur.val != key) cur = cur.next; if(cur.prev == fake) return; //已经是第一个节点 //取出当前节点 if(cur.next != null){ cur.next.prev = cur.prev; cur.prev.next = cur.next; }else{ // cur 为当前最后一个节点 tail = cur.prev; //先改变尾节点的位置 cur.prev.next = null; } //将该节点插到fake之后 insert2head(cur); } public void insert2head(ListNode cur){ //插入到 fake 之后 // 1) fake 之后为空 if(fake.next == null){ fake.next = cur; cur.prev = fake; tail = cur; return; } // 2)正常插入 cur.next = fake.next; cur.prev = fake.next.prev; fake.next.prev = cur; fake.next = cur; } public int get(int key) { //包含该 key,代表使用了当前 key,将其放到最前边 if(map.containsKey(key)){ rm2front(key); return map.get(key); } return -1; } public void set(int key, int val) { if(map.containsKey(key)){ map.put(key, val); rm2front(key); }else{ map.put(key,val); insert2head(new ListNode(key)); if(map.size() > capacity){ //超过容量,尾指针前移 map.remove(tail.val); // 先移除map里的元素 tail = tail.prev; tail.next.prev = null; tail.next = null; } } } } class ListNode{ ListNode prev; ListNode next; int val; ListNode (int val){ this.val = val; }
3. 生成长度为 n 的格雷码
思路: 后面的格雷码等于其相邻的前面的格雷码按顺序书写,加前缀0,再按逆序书写,加前缀1。
//生成长度为 n 的格雷码 public static String[] graycode(int n){ if(n == 1){ String[] str = {"0", "1"}; return str; } // 长度 n 的格雷码有 2^n 个 String[] code = new String[(int)Math.pow(2,n)]; int len = code.length ; String[] last = graycode(n-1); for(int i = 0 ; i < last.length ; i ++){ code[i] = "0" + last[i]; code[len-1-i] = "1" + last[i]; } return code; }
4. 生成 4 7 幸运数字
4和7是两个幸运数字,我们定义,十进制表示中,每一位只有4和7两个数的正整数都是幸运数字,前几个幸运数字为:4,7,44,47,74,77,444,447······
输入: 第一行一个数字T(T <= 1000)表示测试数据的组数,对于每组测试数据,输出一个数K(1 <= K <= 10(18) 10的18次幂)
注意:不同于格雷码,这个没有位置对称这一说
分析:幸运数字的集合4,7,44,47,74
可以看做为0,1,00,01,10,但是这样不能转成对应的十进制表示,我们在转换后的每个二进制数前面加一个1,
变成10,11,100,101,110,这样转换成十进制之后就为2,3,4,5,6,
这就相当于原来的幸运数字顺序从1,2,3,4,5变成了2,3,4,5,6
思路:当求第N个幸运数字时,例如 N=100,我们先让 N+1=101,
对应的二进制数为1100101,然后去掉二进制的第一个数1,
则变成了100101,然后将0和1分别用4和7替换,则得到了最终的幸运数字。
public static void main(String[] args){ System.out.println(getKth(1000000000)); } public static String getKth(long K){ //先 + 1 得到其2进制 String binstr = Long.toBinaryString(++ K); // 替换二进制中的 0 1为 4 7 binstr = binstr.replaceAll("0", "4"); binstr = binstr.replaceAll("1", "7"); // 去掉最高位并返回 return binstr.substring(1); }
5. 打印对角线矩阵
有一个二维数组(n*n),写程序实现从右上角到左下角沿主对角线方向打印。
给定一个二位数组arr及题目中的参数n,请返回结果数组。
[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]],4
返回:[4,3,8,2,7,12,1,6,11,16,5,10,15,9,14,13]
// * | 1 2 3 4 | 4 // * | 5 6 7 8 | -----> 3 8 // * | 9 10 11 12| 2 7 12 // * |13 14 15 16| 1 6 11 16 // * 5 10 15 // * 9 14 // * 13 public int[] arrayPrint(int[][] arr, int n) { int[] ret = new int[n * n]; int pos = 0, bound = 1; // 打印上三角与主对角线 for(int col = n-1 ; col >= 0 ; col -- ){ int tmp_col = col; for(int row = 0 ; row < bound ; row ++){ ret[pos ++] = arr[row][tmp_col ++]; } if( col !=0 ) ++ bound; } -- bound ; //打印下三角 for(int row = 1 ; row < n ; row ++){ int tmp_row = row; for(int col = 0 ; col < bound ; col ++ ){ ret[pos ++] = arr[tmp_row ++][col]; } -- bound; } return ret; }
6. 给一个串,打印 huffman 树编码的长度
思路:这个其实很简单,每次通过 PriorityQueue 找出两个最小元素,然后求和,路径长 + 2 即可,一直进行下去
//用 优先 public static int huffman(String str){ char[] chs = str.toCharArray(); int[] nums = new int[256]; for(int i = 0 ; i < chs.length ; i ++){ ++ nums[chs[i]]; } PriorityQueue<Integer> pq = new PriorityQueue<>(); int ret = 0; for(int n : nums){ if(n != 0) pq.add(n); } while (pq.size() > 1){ int x = pq.poll(); int y = pq.poll(); pq.add(x + y); ret += (x + y); } return ret; }
5. 从一个长度为 n 的数组里找出 m 个随机数
从长度为n的非重复数组里随机选择 m个数
首先在下标 [0...n] 之间随机生成一个下标,将这个下标对应的数和数组的第一个数交换位置;
然后从小标 [1...n] 之间随机生成一个数,将下标对应的数和数组的第二个数交换位置。
依次这样下去,知道找出m个随机数。
public int[] getNumbers(int[] nums,int n, int m){ Random r = new Random(); int[] ret = new int[m]; int pos = 0; if(m > n) System.exit(0); if(m == n) return nums; for(int i = 0 ; i < m ; i++){ //随机产生 [i...n] 的随机数 int idx = i + r.nextInt( n - i); ret[pos ++] = nums[idx]; //和元素 0 交换位置, int temp = nums[i]; nums[i] = nums[idx]; nums[idx] = temp; } System.out.println(Arrays.toString(ret)); return ret; }
6. 去掉字符串中的多于空格,首先去掉前后的空格,然后将内部的多个空格变为一个
//将字符串中的多个空格 替换为 1个空格。且去掉前后的空格 public String removeSpace(String str) { char[] seq = str.toCharArray(); int lo = 0, hi = 0; while( hi < seq.length) { while(hi < seq.length && seq[hi] == ' ') ++ hi; while(hi < seq.length && seq[hi] != ' ') seq[lo ++] = seq[hi ++]; while(hi < seq.length && seq[hi] == ' ') ++ hi; if(hi < seq.length) seq[lo ++] = ' '; } return new String(seq, 0, lo); }
7. 给定一个数组,该数组进栈,打印该数组所有合法的出栈序列
import java.util.*; public class Main { static List<Integer> list = new ArrayList<>(); //该数据集为待打印的序列 static Stack<Integer> stk = new Stack<>(); // 用一个栈来模拟这个过程 public static void main(String args[]) { Scanner sc = new Scanner(System.in) ; Main m = new Main(); list.addAll(Arrays.asList(1,2,3)); int n = list.size(); List<Integer> opera = new ArrayList<>();// 存入一系列 1 0 ,1代表进栈 0 代表出栈 m.allSeq(opera, n, n, n); } public void allSeq(List<Integer> opera, int push, int pop ,int size) { if(push >= 1) //入栈 { opera.add(1); -- push; allSeq(opera, push, pop, size); ++ push; opera.remove(opera.size()-1); } if(pop >= 1 && pop > push) // 出栈 { opera.add(0); -- pop; allSeq(opera, push, pop, size); ++ pop; opera.remove(opera.size()-1); } if(opera.size() == size * 2) { int pos = 0; for(int n : opera) // 打印合法序列 { if(n == 1) stk.push(list.get(pos ++)); else System.out.print (stk.pop() +" "); } System.out.println(); } } }
8.荷兰国旗问题
荷兰国旗问题,红蓝白是分散的,现在要求写代码将其聚合到一起,要求时间复杂度为 O(n) ,空间为 O(1)
void sortColor(int[] color) { int i = 0, pRed = 0, pWhi = color.length-1; while (i <= pWhi) { if (color[i] == red) // 红色 { int temp = color[pRed]; color[pRed ++] = color[i]; color[i ++] = temp; } else if (color[i] == blue) // 蓝色 { ++ i; // 蓝色不管,红白归位蓝色自然在其位置上 } else if (color[i] == white)// 白色 { int temp = color[pWhi]; color[pWhi--] = color[i]; color[i] = temp; } } }
9. 素数筛
执行过程,很神奇的算法, 从素数 i * i 到 i*j <n 均设定为 非素, 知道 i*i >n 开始统计剩下的变为素数了
//素数筛算法 public int countPrimes(int n) { if( n <= 2) return 0; // 序列为 [0...,n-1] boolean notPrime[] = new boolean[n]; int ret = 0; for(int i = 2 ; i < n; ++ i) { if(!notPrime[i]) // 如果当前数是素数 { ++ ret; // 这里统计素数 if(i > Math.sqrt(n)) continue; for(int j = i * i; j < n; j += i) notPrime[j] = true; // 对非素数打上 true 的标记 } } return ret; }
10. Queue Reconstruction by Height (leetcode 406)
重新对二维数组进行排序
Input: [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] Output: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
思路就是先按照 h 降序排列,h 相同则按照 k 升序排列,然后按照 k 重新插入记了
输入:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
first :[[7,0],[7,1]] , k = 0, k = 1
next:[[7,0],[6,1],[7,1]] k = 1
next:[[5,0][7,0][5,2],[6,1],[7,1]] k = 0,k = 2
end: [[5,0][7,0][5,2],[6,1],[4,4],[7,1]] k = 4
// (h,k) 代表高为 h ,且左边有 k 个高度 >= h 的 public int[][] reconstructQueue(int[][] people) { // 二维数组排序,先按第一个值降序,若第一个值相同,则按第二个值升序 Arrays.sort(people, new Comparator<int[]>(){ @Override public int compare(int[] o1, int[] o2) { return o1[0] != o2[0] ? o2[0]- o1[0] : o1[1] - o2[1]; } }); List<int[]> ret = new LinkedList<>(); for(int [] p : people) { ret.add(p[1], p); //将 p 插入到 位置 p[1] } return ret.toArray(new int[0][0]); }
10. 最长递增子序列
开一个栈,每次取栈顶元素top和读到的元素temp做比较,如果temp > top 则将temp入栈;如果temp < top则二分查找栈中的比temp大的第1个数,并用temp替换它。 最长序列长度即为栈的大小top。
这也是很好理解的,对于x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替换Stack[y],此时的最长序列长度没有改变但序列Q的''潜力''增大了。
举例:原序列为1,5,8,3,6,7
栈为1,5,8,此时读到3,用3替换5,得到1,3,8; 再读6,用6替换8,得到1,3,6;再读7,得到最终栈为1,3,6,7。最长递增子序列为长度4。
// 找到 nums 中的最长上升子序列 public void LIS(int nums[]) { Stack<Integer> stack = new Stack<>(); for(int i = 0; i < nums.length; ++ i) { if(stack.isEmpty()||nums[i] > stack.peek()) { stack.push(nums[i]); } else // 用 nums[i] 替换第一个比 nums[i]大的数 , { int lo = 0, hi = stack.size(); while(lo <= hi) { int mid = lo + (hi -lo)/2; if(nums[i] > stack.get(mid)) { lo = mid + 1; } else { hi = mid - 1; } } stack.set(lo, nums[i]); //替换之 } } System.out.println(stack); }
11.构造 AliasTable 的方法
public void AliasMethod(double p[], int alias[]) { // 构造 p[i] * k 的数组 double pk[] = new double[p.length]; Queue<Integer> small = new LinkedList<>(); Queue<Integer> large = new LinkedList<>(); for(int i = 0; i < p.length; ++ i) { pk[i] = p[i] * p.length; if(pk[i] < 1d) small.offer(i); else large.offer(i); } while(!small.isEmpty() && !large.isEmpty()) { int less = small.poll(); // 找到不够 1 的 int more = large.poll(); // 找到多于 1 的 p[less] = pk[less]; alias[less] = more; pk[more] -= (1 - pk[less]); if(pk[more] < 1) small.offer(more); else large.offer(more); } while(!small.isEmpty()) p[small.poll()] = 1; while(!large.isEmpty()) p[large.poll()] = 1; } public int Sampling(double[] p, int[] alias) { Random rand = new Random(); int i = rand.nextInt(p.length); return rand.nextDouble() < p[i] ? i : alias[i]; } public static void main(String[] args) { double p[] = {0.1, 0.2, 0.3, 0.4}; int alias[] = new int[p.length]; Main m = new Main(); m.AliasMethod(p, alias); Map<Integer,Integer> map = new HashMap<>(); for(int i = 0 ; i < 1000; ++ i) { int s = m.Sampling(p, alias); if(map.containsKey(s)) map.put(s, map.get(s) + 1); else map.put(s, 1); } System.out.println(map); }
12. 迪杰特拉斯 DFS BFS Prime
DIJK
package graph; import java.util.Arrays; public class Dijkstra { static int INF = Integer.MAX_VALUE; /** * 求解点 v0 到图中所有其他节点的最短路径 * @param graph 邻接表 * @param n 节点数目 * @param v0 求 v0 到 */ public static void dijk(int[][] graph, int n, int v0) { int[] dist = new int [n]; // v0到所有节点的最短路径 int[] path = new int [n]; // 最短路径的距离 boolean[] visit = new boolean[n]; // 判断节点已经被访问 //初始化节点 path[v0] = -1; dist[v0] = 0; visit[v0] = true; for(int i = 1 ; i < n ; i ++) // 初始化 dist 数组 { dist [i] = graph[v0][i]; path [i] = dist[i] == INF ? -1 : v0; } for(int i = 0 ; i < n ; i ++) { int min = INF, k = v0; // 从dist 数组中找到一个没被访问的 dist 最小的节点 for(int j = 0 ; j < n ; j ++) { if(!visit[j] && dist[j] < min) { min = dist[j]; k = j; } } visit[k] = true; // 标记为访问 // 以当前节点 k 为中转节点,看未访问节点的 dist 能否更短 for(int j = 0 ; j < n ; j ++) { if(!visit[j] && graph[k][j] < INF) { if(dist[k] + graph[k][j] < dist[j]) { dist[j] =dist[k] + graph[k][j]; path[j] = k; } } } } System.out.println(Arrays.toString(path)); } public static void main(String[] args) { int U = INF; // 有向图 int graph[][] = { {U, 4, 6, 6, U, U, U}, {U, U, 1, U, 7, U, U}, {U, U, U, U, 6, 4, U}, {U, U, 2, U, U, 5, U}, {U, U, U, U, U, U, 6}, {U, U, U, U, 1, U, 8}, {U, U, U, U, U, U, U} }; dijk(graph, graph.length , 0); } }
DFS BFS
package graph; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; // 图的遍历 public class Graph_DFS_BFS { static int INF; // 邻接表,和标记数组 public static void DFSTraverse(int[][] graph, int n) { boolean[] visit = new boolean[n]; for (int i = 0; i < n; i++) { if (visit[i] == false) // 当前顶点没有被访问 { DFS(graph, i, visit, n); } } } // 图的深度优先递归算法 static void DFS(int[][] graph, int i, boolean[] visit, int n) { visit[i] = true; // 第i个顶点被访问 System.out.print(i + " "); for (int j = 0; j < n; j++) { if (visit[j] == false && graph[i][j] == 1) { DFS(graph, j, visit, n); } } } // 图的广度遍历操作 public static void BFSTraverse(int[][] graph, int n) { boolean[] visit = new boolean[n]; Queue<Integer> queue = new LinkedList<Integer>(); for (int i = 0; i < n; i++) { if (visit[i] == true) continue; visit[i] = true; System.out.print(i + " "); queue.add(i); while (!queue.isEmpty()) { int j = queue.poll(); for (int k = 0; k < n; k++) { if (graph[j][k] == 1 && visit[k] == false) { visit[k] = true; System.out.print(k + " "); queue.add(k); } } } } } // 测试 public static void main(String[] args) { int U = INF; int[][] graph = { { U, 1, U, U, U, 1, 1, U, U }, { 1, U, 1, U, U, U, 1, U, 1 }, { U, 1, U, 1, U, U, U, U, 1 }, { U, U, 1, U, 1, U, 1, 1, 1 }, { U, U, U, 1, U, 1, U, 1, U }, { 1, U, U, U, 1, U, 1, U, U }, { U, 1, U, 1, U, 1, U, 1, U }, { U, U, U, 1, 1, U, 1, U, U }, { U, 1, 1, 1, U, U, U, U, U } }; System.out.println("图的深度遍历操作(递归):"); DFSTraverse(graph, graph.length); System.out.println("\n-------------"); System.out.println("图的广度遍历操作:"); BFSTraverse(graph, graph.length); System.out.println(); } }
Prime
package graph; public class Prime { static int INF = Integer.MAX_VALUE; /** * @param graph 邻接矩阵 * @param n 节点数 * @param v0 从 v0 开始产生 最小生成树 */ public static void prim(int[][] graph, int n , int v0) { int sum = 0; // 记录总的开销 int[] cost = new int[n]; //保存当前生成树到没访问节点的权值 int[] adj = new int[n]; // 保存相关顶点的下标 // cost 的值为0,在这里就是此下标的顶点已经加入生成树 cost[0] = 0; adj [0] = 0; for(int i = 1 ; i < n ; i ++) { //跳过没有边连接的点 cost[i] = graph[v0][i]; adj[i] = v0; // 初始化为 v0 的下标 } for(int i = 1 ; i < n ; i ++) { int min = INF, k = v0; for(int j = 1 ; j < n ; ++ j) { if(cost[j] != 0 && cost[j] < min) { min = cost[j]; k = j; } } // 这里权值 可以根据 adj 数组去求, if(cost[k] != 0) sum += min; // 更新总权值 System.out.println("arc :" +adj[k]+ "->" + k + ", w : " + min); cost[k] = 0; // 标记此节点以完成任务 //找出下一次迭代的候选边 for(int j = 1 ; j < n ; ++ j) { if(cost[j] != 0 && graph[k][j] < cost[j]) { cost[j] = graph[k][j]; adj [j] = k; } } } System.out.println("total cost : " + sum); } public static void main(String[] args) { //带权图,不能的都设置为 INF int U = INF; int[][] graph = { { U, 6, 1, 5, U, U }, { 6, U, 5, U, 3, U }, { 1, 5, U, 5, 6, 4 }, { 5, U, 5, U, U, 2 }, { U, 3, 6, U, U, 6 }, { U, U, 4, 2, 6, U },}; prim(graph, graph.length, 0); } }