算法刷题收获
序言
先达到算法的广度,有了一定的算法积累,才会有自己的一些想法和见解,在认识上和解题思路上才会idea如泉涌,才会有一定的深度。多看(看题,看题解,看视频题解)多练(将算法的代码多手写几遍)
java API调用
数字
纯数学运算
%取余,/除法,int carry进位
- 回文数:判断一个整数是否是回文数
public boolean isPalindrome(int x) {
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int a = 0;
while (x > a) {
a = a * 10 + x % 10;
x = x / 10;
}
return x == a || x == a / 10;
}
数字字符转换
字符和数字根据ASCII码表可以进行相互转换【数字可以当作字符来使用,字符也可以当作数字来使用】
char num=65; //num='A'
int num='0'; //num=48
int num='9'-'0'; //num=9
进制相加【整数/小数类型的字符串相加】
public class Solution {
public static void main(String[] args) {
addIncludeFloat("1.88", "2.88");
}
/**
* 进制相加
*/
static String numberSystemConversion(String s1, String s2) {
int i = s1.length() - 1;
int j = s2.length() - 1;
int carry = 0;
StringBuilder res = new StringBuilder();
while (i >= 0 || j >= 0 || carry != 0) {
if (i >= 0) carry += (s1.charAt(i--) - '0');
if (j >= 0) carry += (s2.charAt(j--) - '0');
res.append(carry % 9);
carry = carry / 9;
}
return res.reverse().toString();
}
/**
* 整数/小数类型的字符串相加
*/
static void addIncludeFloat(String s1, String s2) {
int index1 = s1.indexOf('.');
int index2 = s2.indexOf('.');
String int1 = s1.substring(0, index1);
String int2 = s2.substring(0, index2);
String float1 = s1.substring(index1 + 1, s1.length());
String float2 = s2.substring(index2 + 1, s2.length());
StringBuilder res = new StringBuilder();
String intSum = numberSystemConversion(int1, int2);
String flrSum = numberSystemConversion(float1, float2);
if (flrSum.length() > Math.max(float1.length(), float2.length())) {
res.append(numberSystemConversion(intSum, "1") + "." + flrSum.substring(1));
} else {
res.append(intSum + "." + flrSum);
}
System.out.println(res);
}
}
整数反转
整数转字符,string转StringBuilder,字符转整数
public void reverse() {
int x = -120;
String numStr = x + ""; //整数转字符
StringBuilder numStrOpt = new StringBuilder(numStr); //string转StringBuilder
String res = numStrOpt.reverse().toString();
if (res.charAt(0) == '0') res = res.substring(1);
if (res.charAt(res.length() - 1) == '-') res = "-" + res.substring(0, res.length() - 1);
//字符转整数try(){Integer.parseInt(res);}
System.out.println(res);
}
完全平方数
https://leetcode-cn.com/problems/perfect-squares/
丑数
字符串
正则表达式
zhangsan@qq.com
youx123@163.net
292133234@qq.com
//String regex = "\\w+@\\w+\\.[a-z]+(\\.[a-z]+)?"; 匹配邮箱的正则表达式
Pattern.matches(".*\\d", "abc2"); //结果为:true 解释:.*可以匹配任何字符串,它相当于(.)*
Pattern.matches("\\d+","2223aa");//字符串能否匹配 结果: false
System.out.println(Pattern.matches("\\d+[a-zA-Z]+","2223aa")); //结果: true
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("aa23ett55");
/**
* matcher
* .start()返回匹配到的子字符串在字符串中的索引位置.
* .end()返回匹配到的子字符串的最后一个字符在字符串中的索引位置.
* .group()返回匹配到的子字符串【要先进行matcher.find()判断操作】
*/
if(matcher.find()) System.out.print(matcher.group()); //23
while(matcher.find()) System.out.print(matcher.group()+"\t");//查找并获取匹配到的内容 结果:23 55
String[] str=pattern.split("电话:0514邮箱:a@a.com");//按模式切割数组 结果:["电话:","邮箱:a@a.com"]
字符串转换整数
使用正则
public int myAtoi(String str) {
/**
* " -42" 输出: -42
* "4193 with words" 输出: 4193
* "words and 987" 输出: 0
* "-91283472332" 输出: -2147483648 【整数溢出】
*/
String regex = "^\\s*([-+]?\\d+){1}";
Matcher matcher = Pattern.compile(regex).matcher(str);
if (matcher.find()){
String group = matcher.group().trim();
try {
return Integer.parseInt(group);
}catch (Exception e){
if(group.contains("-")){
return Integer.MIN_VALUE;
}else return Integer.MAX_VALUE;
}
}else {
return 0;
}
}
使用确定自动机
【判断字符是否是数字Character.isDigit(’ '),字符串转整数溢出的情况可以用long长整型变量来解决这一问题,long ans > 1L + Integer.MAX_VALUE】
origin link
public int myAtoi1(String str) {
str = str.trim();
if (str.length() == 0) return 0;
if (!Character.isDigit(str.charAt(0))
&& str.charAt(0) != '-' && str.charAt(0) != '+')
return 0;
long ans = 0L;
boolean neg = str.charAt(0) == '-';
int i = !Character.isDigit(str.charAt(0)) ? 1 : 0;
while (i < str.length() && Character.isDigit(str.charAt(i))) {
ans = ans * 10 + (str.charAt(i++) - '0');
if (!neg && ans > Integer.MAX_VALUE) { // 2147483647
ans = Integer.MAX_VALUE;
break;
}
if (neg && ans > 1L + Integer.MAX_VALUE) { // -2147483648
ans = 1L + Integer.MAX_VALUE;
break;
}
}
return neg ? (int) -ans : (int) ans;
}
正则表达式匹配【dp/回溯】
- 有 * 【首字符匹配(*匹配多个)||首字符不匹配(*匹配0个)】,无 *【首字符匹配&&后续都要匹配】
- 程序终结的条件:【模式串为空,匹配串不为空,结果:fasle】,【模式串为空,匹配串为空,结果:true】
- 不断缩小字符串,重复执行以上步骤。
public boolean isMatch(String s, String p){
if (p.isEmpty()) return s.isEmpty();
boolean firstMatch=(!s.isEmpty()&&(s.charAt(0)==p.charAt(0)||p.charAt(0)=='.'));
if (p.length()>1&&p.charAt(1)=='*'){
return (firstMatch&&isMatch(s.substring(1),p))||isMatch(s,p.substring(2));
}else {
return firstMatch&&isMatch(s.substring(1),p.substring(1));
}
}
递归
阶乘【普通递归和尾递归的实现】
//一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的
//当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归
/**
* 普通递归(这步计算需要依赖递归函数的返回结果)
*/
int Factorial(int n){
if (n==1) return 1;
else return n*Factorial(n-1); //尾递归的递归函数,但不是递归调用
}
/**
* 尾递归【Factorial_TailRecursion(4,1)】
* (这步计算不需要依赖递归函数的返回结果)
*/
int Factorial_TailRecursion(int n, int a)
{
if (n < 0)
return 0;
else if (n == 0)
return 1;
else if (n == 1)
return a;
else
return Factorial_TailRecursion(n - 1, n * a);
//它是尾递归的递归函数,并且也是递归调用的
// 【当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的,
// 这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。】
}
寻找两个有序数组的中位数
orgin link
两个排好序的数组。都中间分一下,总体的中位数肯定是在二分后4块数据中的两块
[1,3,5,7],[2,4,6,8]
1的中位数是3,5
2的中位数是4,6
和起来[3,4,5,6]的中位数4,5就是两个有序数组的中位数
二分查找:即夹逼法
输出比赛匹配对
NBA比赛球队对战顺序(限制输入n是2的次方数)leedcode544.输出比赛匹配对
由于n限定了是2的次方数,那么就是可以一直对半分的,比如开始有n队,第一拆分为n/2对匹配,然后再对半拆,就是n/2/2,直到拆到n为1停止,而且每次都是首与末配对,次首与次末配对
String findContestMatch(int n) {
List<String> v = new ArrayList<>();
for (int i = 1; i <= n; ++i) v.add(String.valueOf(i));
helper(n, v);
return v.get(0);
}
void helper(int n, List<String> v) {
if (n == 1) return;
for (int i = 0; i < n; ++i) {
v.set(i, "(" + v.get(i) + "," + v.get(n - i - 1)+ ")");
}
helper(n / 2, v);
}
698. 划分为k个相等的子集
https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets/
视频讲解
private boolean backtracking(int[] nums, int k, int target, int cur, int start, boolean[] used) {
// 返回条件
if (k == 0) return true;
if (cur == target) {
// 构建下一个集合
return backtracking(nums, k-1, target, 0, 0, used);
}
for (int i = start; i < nums.length; i++) {
if (!used[i] && cur+nums[i] <= target) {
used[i] = true;
if (backtracking(nums, k, target, cur+nums[i], i+1, used)) return true;
used[i] = false;
}
}
return false;
}
public boolean canPartitionKSubsets(int[] nums, int k) {
// 注意nums[i] > 0
int sum = 0, maxNum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (maxNum < nums[i]) maxNum = nums[i];
}
if (sum % k != 0 || maxNum > sum/k) return false;
boolean[] used = new boolean[nums.length];
return backtracking(nums, k, sum/k, 0, 0, used);
}
动态规划
最长回文子串
public void longestPalindrome() {
String s="babbad";
boolean[][] dp = new boolean[s.length()][s.length()];
int max = 0;
int left=0;
int right=0;
for (int i = 1; i < s.length(); i++) {
for (int j = 0; j < i; j++) {
dp[i][j] =( s.charAt(i) == s.charAt(j) && (i - j < 3 ||dp[i - 1][j + 1]));
if (dp[i][j]&&i - j > max){
max = i - j;
left=j;
right=i;
}
}
}
System.out.println(s.substring(left,right+1));
}
回溯
最大层内元素和
bfs实现
class Solution {
public int maxLevelSum(TreeNode root) {
//用TreeMap来实现 层内元素之和 最大 的那几层中层号最小的那层的层号【比如2层,3层的和都是10,那么返回2】
TreeMap<Integer, Integer> treeMap = new TreeMap<>(); //key:每层的和,value,所处的层
Queue<TreeNode>queue=new LinkedList<>();
queue.add(root);
int h=0;
while (!queue.isEmpty()) {
int len = queue.size();
int curSum=0;
for (int i = 0; i <len; i++) {
TreeNode curNode = queue.poll();
/**
*主要代码就是
**/
if (curNode!=null) {
curSum += curNode.val;
if (curNode.left != null) queue.add(curNode.left);
if (curNode.right != null) queue.add(curNode.right);
}
}
++h;
if (!treeMap.containsKey(curSum)){treeMap.put(curSum,h);}
}
return treeMap.get(treeMap.lastKey());
}
}
dfs实现
class SolutionTest {
int n = 10000;
int[] levelSum = new int[n];
public void inorder(TreeNode node, int level) {
if (node != null) {
inorder(node.left, level + 1);
levelSum[level] += node.val; //解题的关键代码
inorder(node.right, level + 1);
}
}
@Test
public void maxLevelSum() {
TreeNode root=new TreeNode(0).generate();
inorder(root, 1);
int maxIdx = 0;
for (int i = 0; i < n; ++i)
maxIdx = levelSum[i] > levelSum[maxIdx] ? i : maxIdx; //通过标记最大值的下标,实现一次遍历找到最大值
System.out.println(maxIdx);
}
}
BFS和DFS的区别
对于结点的全遍历,二者性能相当。
对于求最短路径,BFS快,你想啊,BFS从起点开始一圈圈往外扩,但凡见到终点即结束遍历,大概率不会把结点全遍历完。但是对于DFS,必须每条路径都走一遍,经过所有路径长度比较后,才敢确定最短的那条,所以,DFS一定要遍历完所有结点,时间当然长了。
DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。【可以进行剪支优化】
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
BFS例题:https://leetcode-cn.com/circle/article/oruvvf/
最大层内元素和【BFS和DFS都可以】
回溯法解数独:https://leetcode-cn.com/circle/article/p24cTv/
BFS【解最短路径类问题】
算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索和s距离为k的所有顶点,然后再去搜索和S距离为k+l的其他顶点。
为了保持搜索的轨迹,宽度优先搜索为每个顶点着色:白色、灰色或黑色。算法开始前所有顶点都是白色,随着搜索的进行,各顶点会逐渐变成灰色,然后成为黑色。在搜索中第一次碰到一顶点时,我们说该顶点被发现,此时该顶点变为非白色顶点。因此,灰色和黑色顶点都已被发现,但是,宽度优先搜索算法对它们加以区分以保证搜索以宽度优先的方式执行。若(u,v)∈E且顶点u为黑色,那么顶点v要么是灰色,要么是黑色,就是说,所有和黑色顶点邻接的顶点都已被发现。灰色顶点可以与一些白色顶点相邻接,它们代表着已找到和未找到顶点之间的边界。
用队列去实现,不需要递归
BFS在求解最短路径或者最短步数上有很多的应用
闯迷宫
在一个nn的矩阵里走,从原点(0,0)开始走到终点(n-1,n-1),只能上下左右4个方向走,只能在给定的矩阵里走,求最短步数。nn是01矩阵,0代表该格子没有障碍,为1表示有障碍物。
int mazeArr[maxn][maxn]; //表示的是01矩阵
int stepArr[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; //表示上下左右4个方向
int visit[maxn][maxn]; //表示该点是否被访问过,防止回溯,回溯很耗时。
public class Test {
public static void main(String[] args) {
System.out.println(bfs(4));
}
static int mazeArr[][] = new int[][]{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}, {1, 0, 0, 0}}; //表示的是01矩阵(4,4)
static int[][] stepArr = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; //表示左右上下4个方向 ,(4,2)的二维数组【一个节点左右上下都尝试走一遍后将其加入到队尾,再从队列弹出下一个节点】
static int[][] visit = new int[4][4]; //表示该点是否被访问过,防止回溯,回溯很耗时。
private static int bfs(int n) {
Node node = new Node(0, 0, 0);
Queue<Node> queue = new LinkedList<Node>();
queue.add(node);
while (!queue.isEmpty()) {
Node newNode = queue.poll();
visit[newNode.x][newNode.y] = 1;
for (int i = 0; i < 4; i++) {
int x = newNode.x + stepArr[i][0];
int y = newNode.y + stepArr[i][1];
if (x == n - 1 && y == n - 1) {
return newNode.step + 1;
}
if (x >= 0 && y >= 0 && x < n && y < n
&& visit[x][y] == 0 && mazeArr[x][y] == 0) {
Node next = new Node(x, y, newNode.step + 1);
queue.add(next);
}
}
}
return -1;
}
private static class Node {
private int x;
private int y;
private int step;
public Node(int x, int y, int step) {
super();
this.x = x;
this.y = y;
this.step = step;
}
}
}
完全平方数
public int numSquares(int n) {
//1. 初始化三元素
//队列:先入先出的容器;
Queue<Integer> queue=new LinkedList<>();
//已访问集合:为了避免队列中插入重复的值
HashSet<Integer> visited=new HashSet<>(); //【创建 set 来存放非重复的元素】
//节点:最好写成单独的类,比如本例写成 (value,step) 元组。也可写成 (value,visited),看自己喜好和题目;
int count=0; //【定义 count 记录完全平方数的个数】
queue.offer(n); //在容量已满的情况下,add() 方法会抛出IllegalStateException异常,offer() 方法只会返回 false
visited.add(n);
while(!queue.isEmpty()){
count++; // 【每次有元素入队就代表还有剩余子平方数】
int len=queue.size(); //当层次遍历第二层是先记录下第一层的个数3【先固定下来】
for(int i=0;i<len;i++){ //不能写成i<queue.size();
//2. 操作队列 —— 弹出队首节点:
int curNode=queue.poll();//【1{1,4,9},4{1,4,9},9{1,4,9}】外层的1,4,9是在队列里面的。该层循环不能省略
//3. 操作弹出的节点 —— 根据业务生成子节点(一个或多个):
for(int j=1;j*j<=curNode;j++){ // 【从 1 开始取,每次拿平方数来比较】
int nextNode=curNode-j*j; // 【用当前结点减去平方数 1,4,9...】
//4. 判断这些节点 —— 符合业务条件,则return,
if(nextNode==0) // 【找完所有的平方数即可返回】
return count;
//不符合业务条件,且不在已访问集合,则追加到队尾,并加入已访问集合:
if(!visited.contains(nextNode)) { // 【如果 set 里面没有存放当前元素,则可以入队,入 set】
queue.offer(nextNode);
visited.add(nextNode);
}
}
}
}
//5. 若以上遍历完成仍未return,那就返回未找到代码(0):
return 0;
}
DFS
可以用栈实现,也可以用递归实现
leedcode例题
class Solution {
List<String>res=new ArrayList<>();
Map<Character,String>map= new HashMap<>();
void dfs(String digits,StringBuilder sb,int h){
if (sb.toString().length()==h) {res.add(sb.toString());return;}
if (digits.isEmpty()) return;
String s = map.get(digits.charAt(0));
for (int j = 0; j < s.length(); j++) {
sb.append(s.charAt(j));
dfs(digits.substring(1),sb,h);
sb.deleteCharAt(sb.length()-1);
}
}
public List<String> letterCombinations(String digits) {
if(digits.isEmpty()) return res;
map.put('2',"abc");map.put('3',"def");map.put('4',"ghi");map.put('5',"jkl");map.put('6',"mno");map.put('7',"pqrs");map.put('8',"tuv");map.put('9',"wxyz");
dfs(digits,new StringBuilder(),digits.length());
return res;
}
}
代码主要还是参考排列组合中的回溯法的实现的代码
public int maxLevelSum(TreeNode root) {
//用TreeMap来实现 层内元素之和 最大 的那几层中层号最小的那层的层号【比如2层,3层的和都是10,那么返回2】
TreeMap<Integer, Integer> treeMap = new TreeMap<>(); //key:每层的和,value,所处的层
Queue<TreeNode>queue=new LinkedList<>();
queue.add(root);
int h=0;
while (!queue.isEmpty()) {
int len = queue.size();
int curSum=0;
for (int i = 0; i <len; i++) {
TreeNode curNode = queue.poll();
/**
*主要代码就是
**/
if (curNode!=null) {
curSum += curNode.val;
if (curNode.left != null) queue.add(curNode.left);
if (curNode.right != null) queue.add(curNode.right);
}
}
++h;
if (!treeMap.containsKey(curSum)){treeMap.put(curSum,h);}
}
return treeMap.get(treeMap.lastKey());
}
四数之和【leedcode超时了】
public List<List<Integer>> fourSum(int[] nums, int target) {
if(nums.length==0) return new ArrayList<>();
Arrays.sort(nums);
dfs(nums,new ArrayList<>(),4,0,target);
List<List<Integer>>s=new ArrayList<>(result_set);
return s;
}
// 建立去重的容器
Set<List<Integer>> result_set = new TreeSet<>(new Comparator<List<Integer>>() {
@Override
public int compare(List<Integer> o1, List<Integer> o2) {
for (int i = 0; i < o1.size(); i++) {
if (!o1.get(i).equals(o2.get(i))) return o1.get(i) - o2.get(i);
}
return 0;
}
});
void dfs(int[] nums,List<Integer>list,int k,int start,int target){
if(list.size()==k&&list.get(0)+list.get(1)+list.get(2)+list.get(3)==target) {result_set.add(new ArrayList<>(list));return;}
for (int i =start ; i <nums.length ; i++) {
list.add(nums[i]);
dfs(nums,list,k,i+1,target);
list.remove(list.size()-1);
}
}
总结:
回溯法:【多叉树+栈/队列】来分析
dp:【二维表/一维表】
递归:
二分:
多指针: