常见算法总结
文章目录
- 常见算法总结
- (1)找出数组中任意重复的数字
- (2) 二维数组中查找
- (3)替换空格
- (4) 从尾到头打印链表
- (5)重建二叉树
- (6)反转二叉树
- (7) 填充二叉树的左右指针
- (8) 将二叉树展开称为链表
- (9)用两个栈实现队列
- (10) 斐波那契数列
- (11) 旋转数组中最小数字
- (12)矩阵中的路径
- (13)机器人的运动范围
- (14)打印二叉树
- (15)打印二叉树
- (16)打印二叉树
- (17) 剪绳子
- (18)最近最少使用算法
- (19) LFU 算法
- (20) 序列化和反序列化二叉树
- (21) topN
- (22) 二叉搜索树的后续遍历
- (23) 二叉树中和为某一值的路径
- (24) 复杂链表的复制
- (25)礼物的最大价值
- (26) 数据流中的中位数
- (27)
- (28) 求 1+ 2 +3 ....+n
- (29) 把字符串转换为整数
- (30) 队列的最大值
- (31)二叉搜索树中的第k大的数字
- (32)不用加减乘除做加法
- (33) 第一个只出现一次的字符
- (34)两个链表的第一个公共节点
- (35)数组中数字出现的次数
- (36) 数组中数字出现的次数II
- (37) 滑动窗口的最大值 (单调队列)
- (38)平衡二叉树
- (39) 二叉搜索树的最近公共祖先
- (40) 二叉树的最近公共祖先
- (41) 在排序数组中查找数字
- (42)最长公共子序列问题
- (43) 把数组排列成最小的数
- (44) 括号生成
- (44)两数之和
- (45)三数之和
- (46)四数之和
- (47)k数之和
- (48)打家劫舍
- (50) 打家劫舍II
- (51)打家劫舍 III
- (51)岛屿的最大面积
- (52)被围绕的区域
- (53) 合并区间
- (54)打开转盘锁
- (55)单调栈 (next greater number)
- (56) 跳跃游戏
- (57)跳跃游戏II
- (58) 最长回文子串
- (59) 最长回文子串II
- (60) 下一个更大元素 (单调栈、单调队列问题)
- (61) 数组中是否有环
- (62) 环形链表的环的入口
- (63) 最长上升子序列(子序列是不连续的)
- (64)无重复字符的最长子串
- (65)盛水最多的容器
(1)找出数组中任意重复的数字
int findRepNums(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
//初始化
for (int i = 0; i < nums.length; i++) {
// 将i作为key存放 value放出现次数当出现次数>0 则找到可行解
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
for (int j=0;j<nums.length;j++) {
if(map.get(nums[j]) == 2 ) {
return nums[j];
}
}
return -1;
}
(2) 二维数组中查找
n*m的数组中,从上到下从左到右都是递增的。在矩阵中搜索目标值。
static boolean findNumInArray(int[][] array, int target) {
// 从斜对角开始搜索,此时数据类似一颗二叉树
if (array.length == 0 || array[0].length == 0) {
return false;
}
int m = array.length;
int n = array[0].length;
for (int i = 0, j = n - 1; i < m &&j >= 0;) {
if(array[i][j] ==target) {
return true;
}
if(array[i][j] > target) {
i++;
} else {
j--;
}
}
return false;
}
(3)替换空格
String replaceSpace(String s) {
return s.replaceAll(" ","%20");
}
String replaceSpace(String s) {
char[] chars = s.toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for (char c : chars) {
stringBuilder.append(c== ' ' ? "20%" : c);
}
return stringBuilder.toString();
}
(4) 从尾到头打印链表
static int[] reversePrintListNode(ListNode head) {
List<Integer> list = new ArrayList<>();
while (head != null) {
list.add(head.getVal());
head = head.next;
}
int size = list.size();
int[] array = new int[size];
for (Integer num : list) {
size--;
array[size] = num;
}
return array;
}
(5)重建二叉树
已知二叉树的前序和中序遍历的结果重构二叉树
Input preorder[3,9,20,15,7]
output inorder[9,3,15,20,7]
二叉树类型的题目分为两类一类是遍历的思维模式、一类是递归(分解问题)的思维模式;
前序遍历可以知道其根节点 preorder[0] 通过这个值可以从中序遍历中判断左右子树
private TreeNode buildTwoTree(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
if (preStart > preEnd) {
return null;
}
TreeNode root = new TreeNode(preorder[preStart]);
int index = 0;
for (int i = 0; i < inorder.length; i++) {
if (inorder[i] == root.getVal()) {
index = i;
break;
}
}
root.left = buildTwoTree(preorder, preStart + 1,
preStart + index - inStart, inorder, inStart, index - 1);
root.right = buildTwoTree(preorder, preStart + index - inStart + 1,
preEnd, inorder, index + 1, inEnd);
return root;
}
(6)反转二叉树
输入二叉树的根节点将整个树镜像反转
思路就是遍历每一个节点让所有节点的左右子树交换位置
private TreeNode traverse(TreeNode root) {
if (root == null) {
return null;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
traverse(root.left);
traverse(root.right);
return root;
}
递归思路:先将左子树反转,再将右子树反转,最后将根节点的左右子树反转
private TreeNode invert(TreeNode root) {
if (root == null) {
return null;
}
TreeNode root.left = invert(root.left);
TreeNode root.right = invert(root.right);
root.left = right;
root.right = left;
return root;
}
(7) 填充二叉树的左右指针
TreeNode connect(TreeNode root) {
if(root == null ) return null;
traverse(root.left,root.right);
return root
}
void traverse(TreeNode node1,TreeNode node2) {
if(node1==null || node2 == null) {
return null;
}
node1.next = node2;
// 连接相同父节点
traverse(node1.left,node1.right);
traverse(node2.left,node2.right);
traverse(node1.left,node2.right);
}
(8) 将二叉树展开称为链表
void flatten(TreeNode root) {
if(root == null)
flatten(root.left);
flatten(root.right);
TreeNode left = root.left;
TreeNode right = root.right;
// 将左子树接到右子树上
root.left = null;
root.right = left;
// 将右子树的节点加入左子树的末尾
TreeNode p = root;
while(p.right !=null ) {
p = p.right;
}
p.right = right;
}
(9)用两个栈实现队列
public class Queue {
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
public Queue() {
}
public void appendTail(int val) {
stack1.push(val);
}
public int deleteHead() {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
return stack2.isEmpty() ? -1 : stack2.pop();
}
}
(10) 斐波那契数列
/***
* 递归解法
* @param n
* @return
*/
int F(int n) {
if (n == 0) return 0;
if (n == 1 || n == 2) return 1;
return F(n - 1) + F(n - 2);
}
/**
* 动态规划
*
* @param n
* @return
*/
static int F2(int n) {
if (n < 1) {
return -1;
}
int[] dp = new int[n];
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
for (int i = 3; i < n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n-1];
}
(11) 旋转数组中最小数字
把一个数组最开始的若干个元素搬数组末尾,我们称之为数组的旋转
/**
* 判断旋转数组中的最小值
* [3,4,5,6,0,1,2] mid>right
* [1,0,1,1,1] mid == right
* [2,2,3,4,5,6,6]mid > mid < right
*
* @param nums
* @return
*/
int minArray(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > right) {
left = mid + 1;
} else if (nums[mid] < right) {
right = mid;
} else {
right--;
}
}
return nums[left];
}
(12)矩阵中的路径
给定一个mxn的二维字符网格board 和 一个字符串单词 word。如果 word存在于网格中,返回true;否则返回false。
回溯法模版:
backtrack(路径,选择列表)
if(满足结束条件)
res.add(路径)
return
for(选择 in 选择列表)
做选择
backtrace(路径,选择列表)
撤销选择
/**
* 每一步可以在矩阵中向左、右、
* 上、下移动一格。如果一条路径经过了矩阵的某一格
* @param matrix 原字符数组
* @param visit 已经访问过的标志
* @return
*/
// 表示上下左右对row和col的操作
private final static int[][] next = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
boolean hasPath(char[][] matrix, boolean[][] visit, char[] target) {
int row = matrix.length;
int col = matrix[0].length;
// 路径可以从矩阵中任意一格开始
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (findPath(matrix, visit, target, 0, i, j)) {
return true;
}
}
}
return false;
}
private boolean findPath(char[][] matrix, boolean[][] visit, char[] target, int pathLength, int row, int col) {
if (pathLength == target.length) {
return true;
}
if (col < 0 || col > matrix[0].length || row < 0 || row > matrix.length
|| visit[row][col] || matrix[row][col] != target[pathLength]) {
return false;
}
visit[row][col] = true;
for (int[] i : next) {
if (findPath(matrix, visit, target, pathLength + 1, row + i[0], col + i[1])) {
return true;
}
}
visit[row][col] = false;
return false;
}
(13)机器人的运动范围
地上有mxn个方格,机器人从[0][0] 运动到[m-1][n-1],每次可以上下左右移动一格,不能移动到方格外也不能进入行坐标和列坐标位数之和大于k的格子。
(14)打印二叉树
从左到右的顺序打印二叉树
/**
* 从左到右打印二叉树
*
* @param root
* @return
*/
int[] printOrderTreeNode(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> l = new ArrayList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode treeNode = queue.poll();
l.add(treeNode.getVal());
if (treeNode.left != null) {
queue.offer(treeNode.left);
}
if (treeNode.right != null) {
queue.offer(treeNode.right);
}
}
}
// j将list转为int数组
int n = l.size();
int j = 0;
int[] res = new int[n];
for (Integer i : l) {
res[j++] = i;
}
return res;
}
(15)打印二叉树
从左到右的顺序打印二叉树 每一层打印一行
List<List<Integer>> printOrderTreeNode2(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> l = new ArrayList<>();
queue.offer(root);
List<List<Integer>> result = new ArrayList<>();
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历完一层 加入列表
List<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode treeNode = queue.poll();
list.add(treeNode.getVal());
if (treeNode.left != null) {
queue.offer(treeNode.left);
}
if (treeNode.right != null) {
queue.offer(treeNode.right);
}
}
result.add(list);
}
return result;
}
(16)打印二叉树
从左到右的顺序打印二叉树之字形打印
List<List<Integer>> printOrderTreeNode3(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> l = new ArrayList<>();
queue.offer(root);
List<List<Integer>> result = new ArrayList<>();
int temp = 0;
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历完一层 加入列表
List<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode treeNode = queue.poll();
list.add(treeNode.getVal());
if (treeNode.left != null) {
queue.offer(treeNode.left);
}
if (treeNode.right != null) {
queue.offer(treeNode.right);
}
}
if (result.size() % 2 == 1) Collections.reverse(list);
result.add(list);
}
return result;
}
(17) 剪绳子
长度为n的绳子,把绳子剪成整数长度的m段,每段绳子的长度计为k[0],k[1]…k[m-1].请问这些绳子的乘积最大为多少。
我们考虑动态规划
/**
* 剪绳子的状态变化 -- 》 状态
* dp[i]表示长度为i的绳子的最大乘积
* 剪下一刀后剩下的两段长度是j和i-j,在这个上面还可能继续减(子问题)
* dp[i] = max(dp[j]*dp[i-j])
* base 条件 由于m>1,所以对于n=2和n=3,必须切一刀,返回1和2。
*
* @param
* @return
*/
int cuttingRop(int n) {
if (n == 2) return 1;
if (n == 3) return 2;
int[] dp = new int[n + 1];
//初始化
dp[2] = 2;
dp[3] = 3;
for (int i = 4; i <=n;i++) {
int max = 0;
for (int j = 1;j<=1/2*i;j++) {
max = Math.max(max,dp[i]*dp[i-j]);
}
dp[i] = max;
}
return dp[n];
}
(18)最近最少使用算法
public class LRUCacheTest {
int cap;
// LRU 算法最近最少使用算法
private LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>();
public LRUCacheTest(int cap) {
this.cap = cap;
}
public int get(int key) {
if(!cache.containsKey(key)) {
return -1;
}
// 将key变为最近使用
makeRecently(key);
return cache.get(key);
}
public void put(int key,int val) {
if(cache.containsKey(key)) {
cache.put(key,val);
makeRecently(key);
return;
}
if(cache.size() == cap) {
// 链表头部就是最进最少使用的;
int oldKey = cache.keySet().iterator().next();
cache.remove(oldKey);
}
cache.put(key,val);
}
private void makeRecently(int key) {
// 将节点插入头部
int val = cache.get(key);
cache.remove(key);
cache.put(key,val);
}
}
(19) LFU 算法
(20) 序列化和反序列化二叉树
/**
* 序列化和反序列化二叉树
* 一般情况下需要前序+中序列
* 中序+ 后序才可以重建一颗二叉树但由于
* 我们序列化的时候确定了为null 的位置
* 所以也是可以进行反序列化的
*/
public class SerializeOrDerialize {
String SEP = "";
String NULL = "#";
String Serialize(TreeNode root) {
StringBuilder stringBuilder = new StringBuilder();
serTree(stringBuilder, root);
return stringBuilder.toString();
}
private void serTree(StringBuilder stringBuilder, TreeNode root) {
if (root == null) {
stringBuilder.append(NULL).append(SEP);
return;
}
stringBuilder.append(root.getVal()).append(SEP);
serTree(stringBuilder, root.left);
serTree(stringBuilder, root.right);
}
TreeNode deserialize(String data) {
LinkedList<String> nodes = new LinkedList<>();
for (String s : data.split(SEP)) {
nodes.add(s);
}
return des(nodes);
}
private TreeNode des(LinkedList<String> nodes) {
if(nodes.isEmpty()) return null;
String first = nodes.removeFirst();
if(first.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(first));
root.left = des(nodes);
root.right = des(nodes);
return root;
}
}
(21) topN
private static int[] getTopN(int[] array, int n) {
if (array == null || n == 0) {
return new int[]{};
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue(array.length, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int e : array) {
priorityQueue.offer(e);
}
int[] res = new int[n];
for (int i = 0; i < n; i++) {
res[i] = priorityQueue.poll();
}
return res;
}
(22) 二叉搜索树的后续遍历
输入一个整数数组判断该数组是否为二叉搜索树的后续遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。
/**
* 二叉树后续遍历的特征
* 左子树的值小于根节点;右子树的值大于根节点;
* 思路 先找到根节点再划分左右子树,
*
* @param nums
* @return
*/
private boolean isPostOrder(int[] nums) {
if(nums == null) return false;
return verifyPostOrder(nums, 0, nums.length - 1);
}
private static boolean verifyPostOrder(int[] nums, int start, int end) {
int rootVal = nums[end];
int i = start;
for (; i < end; i++) {
if (nums[i] > rootVal) {
break;
}
}
int j = i;
for (; j < end; j++) {
if (nums[j] < rootVal) {
return false;
}
}
boolean left = true;
if (i > start) {
left = verifyPostOrder(nums, start, i - 1);
}
boolean right = true;
if (i < end) {
right = verifyPostOrder(nums, i + 1, end);
}
return left && right;
}
(23) 二叉树中和为某一值的路径
在这里插入代码片
(24) 复杂链表的复制
/**
* 1.首先将链表节点进行复制A->A'->B->B'->C->C'->NULL
* 2.当前节点的随机指针进行复制(cur.next.random = cur.random.next;)
* 3.将两个链表进行拆分
*
* @return
*/
RandomListNode copyLinkedList(RandomListNode root) {
RandomListNode cur = root;
while (cur != null) {
RandomListNode newNode = new RandomListNode(cur.val);
newNode.next = cur.next;
cur.next = newNode;
cur = newNode.next;
}
//重置cur
cur = root;
while (cur != null) {
if (cur.random != null) {
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
// 拆分
cur = root;
RandomListNode head2 = root.next;
while (cur != null) {
RandomListNode temp = cur.next;
cur.next = temp.next;
if (temp.next == null ) {
temp.next = null ;
} else {
temp.next = temp.next.next;
}
cur = cur.next;
}
return head2;
}
(25)礼物的最大价值
dp数组多扩一列对可以减少对边界值的处理。入下图所示:那么dp[i][j]就相当于是grid[0][0]; 遍历也可以从下标1开始;而新增的这一行默认初始值为0;
/**
* 礼物的最大价值
* dp[i][j] 表示走到i行j列拿到的礼物的最大累计价值
* dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + grid[i][j]
* @param grid
* @return
*/
int maxGiftValue(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m+1][n+1];
for(int i = 1;i<m;i++) {
for(int j =1 ;j<n;j++) {
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
return dp[m][n];
}
(26) 数据流中的中位数
double midNum(int num) {
PriorityQueue<Integer> queueMin = new PriorityQueue<>();
PriorityQueue<Integer> queueMax = new PriorityQueue<>((o1, o2) -> o2 - o1);
if (queueMin.size() == queueMax.size()) {
queueMax.offer(num);
queueMin.offer(queueMax.poll());
} else {
queueMin.offer(num);
queueMax.offer(queueMin.poll());
}
if (((queueMin.size() + queueMax.size()) & 1) == 0) {
return ( queueMin.peek() + queueMax.peek() ) /2.0;
}
return queueMin.peek();
}
(27)
(28) 求 1+ 2 +3 …+n
int sum(int n ) {
int s = n ;
boolean t = n > 0 && (s += sum(n-1)) > 0 ;
return s;
}
(29) 把字符串转换为整数
(30) 队列的最大值
(31)二叉搜索树中的第k大的数字
右中左的顺序遍历
int ans = 0;
private TreeNode midSearch(TreeNode root,int k) {
if(root == null) {
return null;
}
// 遍历右子树
midSearch(root.right,k);
ans++;
if(ans==k) {
return root;
}
midSearch(root.left,k);
return null;
}
(32)不用加减乘除做加法
(33) 第一个只出现一次的字符
private char currentFirst(String s) {
HashMap<Character, Integer> currMap = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
currMap.put(s.charAt(i),currMap.getOrDefault(s.charAt(i),0) +1);
}
for(Character c : currMap.keySet()) {
Integer value = currMap.get(c);
if(value == 1) {
return c;
}
}
return ' ';
}
private char currentFirst2(String s) {
if (s == null || s.length() == 0) {
return ' ';
}
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
int index = s.charAt(i) - 'a';
count[index]++;
}
for(int j =0;j <s.length();j++) {
int index = s.charAt(j) - 'a';
if(count[index] == 1) {
return s.charAt(j);
}
}
return ' ';
}
(34)两个链表的第一个公共节点
两个链表的第一个公共节点,定义双指针,指针1指向head1,指针1走到头之后指向head2;指针2走到头之后指向head1;相遇的节点就是公共节点;
private ListNode someNode(ListNode node1, ListNode node2) {
ListNode p1 = node1;
ListNode p2 = node2;
while (p1 != p2) {
if (p1 != null) {
p1 = p1.next;
} else {
p1=node2;
}
if (p2 != null) {
p2 = p2.next;
} else {
p2=node1;
}
}
return p1;
}
(35)数组中数字出现的次数
两个相同数的异或结果为0;将数据中所有数字进行异或。最后就是两个不同数字异或的结果。
(36) 数组中数字出现的次数II
(37) 滑动窗口的最大值 (单调队列)
单调队列:输入数组nums和一个正整数k,有一个大小为k的窗口在nums上从左至右滑动,请输入每次滑动过程中滑动窗口的最大值。
int[] maxSlidingwindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k == 0) {
return new int[];
}
int index = 0;
int n = nums.length;
int[] res = new int[n - k + 1];
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < n; i++) {
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.addLast(i);
if (deque.peekFirst() == i - k) {
deque.pollFirst();
}
if (i >= k - 1) {
res[index++] = nums[deque.peekFirst()];
}
}
}
(38)平衡二叉树
输入一颗二叉树的根节点,判断该树是不是平衡二叉树,如果某二叉树中任意节点的左右子树的深度不超过1,那么它就是一颗平衡二叉树;
boolean isBlanceTree(TreeNode root) {
if (root == null) {
return true;
}
return (depth(root.left) - depth(root.right))<=1 && isBlanceTree(root.left) && isBlanceTree(root.right);
}
int depth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(depth(root.left), depth(root.right));
}
(39) 二叉搜索树的最近公共祖先
TreeNode searchTreeParent(TreeNode root, TreeNode p, TreeNode q) {
if( (p.val < root.val) && (q.val < root.val)) {
return searchTreeParent(root.right,p,q);
}
if( (p.val > root.val) && (q.val >root.val)) {
return searchTreeParent(root.right,p,q);
}
return root;
}
(40) 二叉树的最近公共祖先
TreeNode commonTreeParent(TreeNode root,TreeNode p,TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = commonTreeParent(root.left,p,q);
TreeNode right = commonTreeParent(root.right,p,q);
if(left == null) return right;
if(right ==null) return left;
return root;
}
(41) 在排序数组中查找数字
统计一个数字在排序数组中出现的次数;
可以查找数字的左边界和右边界;注意:当我们while循环使用 <=时,左右边界需要做越界处理。
int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
int left = searchLeftIndex(nums, target);
int right = searchRightIndex(nums, target);
return right - left + 1;
}
int searchLeftIndex(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
if (left >= nums.length || nums[left] != target) {
return 0;
}
return left;
}
int searchRightIndex(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
if (right <0 || nums[right] != target) {
return 0;
}
return right;
}
(42)最长公共子序列问题
LCS问题,最长公共子序列(子序列可以不连续);类似(25)最大礼物
这里也增加一个二维数组空间。由于两个字符串均为空串,或者其中一个为空的时候dp[0][j] 和 dp[i][0] 的值为0;所以多增加一个数组空间可以使代码更简洁;
/**
* dp[i][j] 表示字符串str1[0,...i-1] 和 str2[0,....j-1]
* 的LCS长度
* 当str[i] == str[j] 时
* dp[i][j] = dp[i-1][j-1]+1
* 否则 dp[i][j] = max(dp[i][j-1],dp[i-1][j])
*
* @param str1
* @param str2
* @return
*/
int LongCommonSeq(String str1, String str2) {
int m = str1.length();
int n = str2.length();
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int dp[][] = new int[m + 1][n + 1];
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (s1[i-1] == s2[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];
}
(43) 把数组排列成最小的数
对于整数a,b,将其转换成字符串a,b后,比较字符串a+b和b+a的大小;
字符串相加正好是可以连接起来的,那么a+b就得到字符串32343,b+a就得到字符串43323,比较的是这两个字符串的大小,可以用ASCII码值进行比较,返回小的那个字符串排列,就相当于找到了数值的排列,这样整体排序完后就可以找到最小的数
String printMinNums(int[] nums) {
if (nums == null || nums.length == 0) {
return "";
}
List<String> res = new ArrayList<>();
for (int num : nums) {
res.add(String.valueOf(num));
}
res.sort((o1, o2) -> (o1 + o2).compareTo(o2 + o1));
StringBuilder stringBuilder = new StringBuilder();
for (String s : res) {
stringBuilder.append(s);
}
return stringBuilder.toString();
}
(44) 括号生成
数字 n 代表生成括号的对数,设计一个函数,用于能够生成所有可能的并且有效的括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
List<String> result = new LinkedList<>();
List<String> generateParenthesis(int n) {
if (n == 0) {
return result;
}
backtrace(n, n, "");
return result;
}
private void backtrace(int left, int right, String s) {
// 边界条件
if (left < 0 || right < 0) {
return;
}
// 剪枝
if (left > right) {
return;
}
// 符合条件的结果
if (left == 0 && right == 0) {
result.add(s);
}
/*回溯模板要遍历选择列表,但是本题的选择列表是两个独立的情况,所以单独写出来就好*/
s = s + "(";
backtrace(left - 1, right, s);
s = s.substring(0, s.length() - 1);
s = s + ")";
// 进入下一层决策
backtrace(left, right - 1, s);
s.substring(0, s.length() - 1);
}
(44)两数之和
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
return new int[]{map.get(nums[i]), i};
}
map.put(target - nums[i], i);
}
return null;
}
双指针法
public static List<List<Integer>> twoSum(int[] nums,int target) {
if(nums == null || nums.length == 0) {
return null;
}
// 对数组进行排序
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int low = 0; int high = nums.length-1;
while(low<high) {
int sum = nums[low] + nums[high];
int lowValue = nums[low];
int highValue = nums[right];
if(sum < target) {
while(low<high && lowValue == nums[low]) low++; // 跳过重复元素
} else if(sum>target) {
while(low<high && highValue == nums[high]) right--; // 跳过重复元素
} else {
List<Integer> temp = new ArrayList<>();
temp.add(low);
temp.add(high);
res.add(temp);
while(low<high && lowValue == nums[low]) low++; // 跳过重复元素
while(low<high && highValue == nums[high]) right--; // 跳过重复元素
}
}
return res;
}
(45)三数之和
可以借助两数之和的结果 三数之和 a+b+c = target 等同于 a+b = target -c;
public List<List<Integer>> ThreeSum(int[] nums,int target) {
for(int i=0;i<nums.length;i++) {
List<List<Integer>> res = twoSum(nums,i+1,target-nums[i]);
for(List<integer> list : res) {
list.add(nums[i]);
}
// 跳过第一个数字
while(i<n-1&& nums[i] == nums[i+1]) i++;
}
}
public List<List<Integer>> twoSum(int[] nums,int start,int target) {
if (nums == null || nums.length == 0) {
return null;
}
// 对数组进行排序
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int low = start;
int high = nums.length - 1;
while (low < high) {
int sum = nums[low] + nums[high];
int lowValue = nums[low];
int highValue = nums[high];
if (sum < target) {
while (low < high && lowValue == nums[low]) low++; // 跳过重复元素
} else if (sum > target) {
while (low < high && highValue == nums[high]) high--; // 跳过重复元素
} else {
List<Integer> temp = new ArrayList<>();
temp.add(lowValue);
temp.add(highValue);
res.add(temp);
while (low < high && lowValue == nums[low]) low++; // 跳过重复元素
while (low < high && highValue == nums[high]) high--; // 跳过重复元素
}
}
return res;
}
(46)四数之和
同上
(47)k数之和
要求k数之和可以通过k-1数之和得到因此可以采用递归的方式求解
Arrays.sort(nums);
List<List<Integer>> nSum(int[] nums,n,start,target) {
int size = nums.length;
if(n<2 || size < n) return res;
if(n==2) { // 两数之和}
else {
for(int i=start;i<size;i++) {
List<List<Integer>> list = nSum(nums,n-1,i+1,target-nums[i]);
for(List<integer> list : res) {
list.add(nums[i]);
}
}
}
while(i<n-1&&nums[i] == nums[i+1]) i++;
}
(48)打家劫舍
public static int rob(int[] nums) {
int len = nums.length;//记录数组长度
if(len == 0)//若没有数据,直接返回0
return 0;
int[] dp = new int[len + 1];//建立dp数组
dp[0] = 0;//初始化
dp[1] = nums[0];
for(int i = 2; i <= len; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]);//填入数据
}
return dp[len];//结果返回
}
(50) 打家劫舍II
public int rob(int[] nums) {
if(nums==null||nums.length==0)
return 0;
if(nums.length==1)
return nums[0];
int[] dp1 = new int[nums.length];
int[] dp2 = new int[nums.length];
dp1[1] = nums[0]; //从第1个房屋开始偷
dp2[1] = nums[1]; //从第2个房屋开始偷
for(int i=2;i<nums.length;i++) {
dp1[i] = Math.max( dp1[i-2]+nums[i-1],dp1[i-1]);
dp2[i] = Math.max( dp2[i-2]+nums[i], dp2[i-1]);
}
return Math.max( dp1[nums.length-1], dp2[nums.length-1] );
(51)打家劫舍 III
树节点上进行的操作
HashMap<TreeNode,Integer> memo = new HashMap<>(); // 备忘录
int rob(TreeNode root) {
if(root == null) {
return 0;
}
if(memo.containsKey(root)) return memo.get(root);
int do_it = root.val + (root.left == null ? 0 : rob(root.left.left)+rob(root.left.right)) + (root.right == null? 0 : rob(root.right.left) + rob(root.right.right));
int notDo_it = rob(root.left) + rob(root.right);
int res = Max.max(do_it,notDo_it);
memo.put(root ,res);
return res;
}
(51)岛屿的最大面积
(52)被围绕的区域
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
从边缘处为O的开始深度优先,所有相连的O都置为Q,这样深度优先结束之后剩下的O就是被X包围的了,然后再遍历每一个元素,将O变成X,将Q变成O还原。
void roundByX(int[][] borad) {
if(borad == null || board.length==0 || board[0].length==0)
{return;}
int m = board.length;
int n = board[0].length;
for(int i = 0;i<m;i++) {
for(int j = 0 ;j<n;j++) {
if((i==0 || i==board.size()-1 || j==0 || j==board[0].size()-1) && board[i][j]=='O'){
core(board, i, j);
}
}
}
for(int i=0; i<board.size(); i++){
for(int j=0; j<board[0].size(); j++){
if(board[i][j] == 'O')
board[i][j] = 'X';
if(board[i][j] == 'Q')
board[i][j] = 'O';
}
}
}
void core(int[][] board,int row,int col) {
if(board[i][j] == 'O'){
board[i][j] = 'Q';
//向上
if(i > 0 && board[i-1][j] == 'O')
core(board, i-1, j);
//向下
if(i < board.size()-1 && board[i+1][j] == 'O')
core(board, i+1, j);
//向左
if(j > 0 && board[i][j-1] == 'O')
core(board, i, j-1);
//向右
if(j < board[0].size()-1 && board[i][j+1] == 'O')
core(board, i, j+1);
}
(53) 合并区间
(54)打开转盘锁
(55)单调栈 (next greater number)
(56) 跳跃游戏
贪心算法:思路就是每次都取最大的步数。条件fastest <=i 说明遇到0不能再跳跃了。
boolean canJump(int[] nums) {
int n = nums.length;
int fastest = 0;
for (int i = 0; i < n-1; i++) {
fastest = Math.max(fastest, i + nums[i]);
if (fastest <= i) {
return false;
}
}
return fastest >= n - 1;
}
(57)跳跃游戏II
如果保证可以跳到最后一格。最少跳多少次?
动态规划:找到状态、选择->明确dp函数含义->寻找状态之间的含义
// 贪心算法
int minJumpNum(int[] nums) {
int n = nums.length;
int fastest = 0 ; // 标记了[i.....end]之间能够跳到的最远距离
int end = 0;
int step = 0;
for(int i=0;i<n-1;i++) {
fastest = Math.max(fastest,i+nums[i]);
if(end == i) {
end = fastest;
step++;
}
}
return step;
}
(58) 最长回文子串
从给定的字符串 s 中找到最长的回文子串的长度。
例如 s = “babbad” 的最长回文子串是 “abba” ,长度是 4 。
public String longestPalindrome(String s) {
if (s == null || s.length() == 0)
return s;
int n = s.length();
char[] str = s.toCharArray();
String res = null;
boolean[][] dp = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
dp[i][j] = str[i] == str[j] && (j - i <= 2 || dp[i + 1][j - 1]);
if (dp[i][j] && (res == null || j - i + 1 > res.length())) {
res = s.substring(i, j + 1);
}
}
}
return res;
}
(59) 最长回文子串II
1、布尔数组dp[i][j]表示子串s【i……j】是否为回文串
2、如何生成dp[i][j]?
2.1、当左右新加入的字符不同时,一定不为回文串,比如原串 “a” 左右新加字符后为“cab”,那这一定不为回文串,即dp[i][j]=false;
2.2、左右新加入的字符相同时,分以下两种讨论:
2.2.1、原串长度<2,则此时一定为回文串,比如:原串为 “a”,加入后串变为“cac”,则dp[i][j]=true;
2.2.2、原串长度>2,则加入两个相等字符看原串的值为false还是true。比如原串为“ac",此时原串对应的dp[i+1][j-1]=false,因为ac不是回文串,当加入了两个相同字符,比如为”bacb“,则dp[i][j]=dp[i+1][j-1]的值,即也为false;
public String longestPalindrome(String s) {
int len=s.length();
if(len<2)
return s;
char[] ch=s.toCharArray();
boolean[][] dp=new boolean[len][len];
for(int i=0;i<len;i++){
for(int j=0;j<len;j++){
dp[i][j]=true;
}
}
int begin=0;
int maxlen=1;
for(int j=1;j<len;j++){
for(int i=0;i<j;i++){
if(ch[i]!=ch[j]){
dp[i][j]=false;
}else{
if(j-i<3){
dp[i][j]=true;
}else{
dp[i][j]=dp[i+1][j-1];
}
}
if(j-i+1>maxlen&&dp[i][j]){
maxlen=j-i+1;
begin=i;
}
}
}
return s.substring(begin,begin+maxlen);
}
(60) 下一个更大元素 (单调栈、单调队列问题)
(61) 数组中是否有环
快慢指针,两指针相遇则有环
boolean hasCycle(ListNode head) {
if(head == null) {
return false;
}
ListNode fast = head;
ListNode slow = head;
while(fast !=null && fast.next!+null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) return true;
}
return false;
}
(62) 环形链表的环的入口
public ListNode detectCycle(ListNode head) {
ListNode slow = head; //慢指针
ListNode fast = head;//快指针
if(head == null ){ //特判
return null;
}
//快指针每次走2步,慢指针每次都走一步
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
//两个点相遇,将快慢指针随便一个扔回头结点,然后同时走,再次相遇就是链表入口节点
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
(63) 最长上升子序列(子序列是不连续的)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
dp[i] 表示以index = i结尾的数组中最长上升子序列;
dp[i]=max(dp[j])+1;
public int lengthOfLIS(int[] nums) {
int n=nums.length;
if(n==0){
return 0;
}
int maxlen=1;
int[] arr=new int[n];
Arrays.fill(arr,1);
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
arr[i]=Math.max(arr[i],arr[j]+1);
}
}
maxlen=Math.max(arr[i],maxlen);
}
return maxlen;
}
(64)无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0) return 0;
// 存放字符
HashMap<Character, Integer> map = new HashMap<Character,Integer>();
int max = 0; // 最大不重复字符长度
int left = 0; // 上一个重复字符所在的位置(数组下标 + 1)
for(int i = 0; i < s.length(); i++) {
// 判断在map 中是否已经保存过字符
if(map.containsKey(s.charAt(i))) {
// 取最大值: map.get(s.charAt(i)) + 1 表示上一个重复字符的位置
left = Math.max(left, map.get(s.charAt(i)) + 1);
}
// 记录每个字符位置,如果重复则覆盖
map.put(s.charAt(i), i);
// 记录最大的不重复字符串长度。
// (i - left + 1 ) == (i+1 - left) 表示:当前阶段最大的不重复字符串长度, + 1 的原因是 left 是从 1开始计算的
max = Math.max(max, i - left + 1);
}
return max;
}
(65)盛水最多的容器
双指针思路:定义左右两个指针。移动当前较低的才有可能比当前面积大。
public int maxArea(int[] height) {
int res = 0;
int left = 0;
int right = height.length - 1;
while (left < right) {
res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return res;
}