目录
1.1 最长公共子序列(牛客版, leetcode 1143)
4.2 删除排序链表中的重复元素(牛客版,leetcode 82)
1. 给定一个 0-4随机数生成器 如何生成0-6随机数(leetcode 470)
1. 动态规划 / 回溯
动态规划三要素:初始化状态,状态转移方程,边界
对于最长序列系列的问题(公共,上升,回文)的解决方案就是动态规划,采取填表的方式,定义dp[i][j]。
公共对应两个字符串str1和str2,i 和 j 分别对应str1的str2的位置,用于比较。
对于单个字符串dp[i][j],指的是该字符串中第 i 位到第 j 位的运算。
1.1 最长公共子序列(牛客版, leetcode 1143)
题目:给定两个字符串str1和str2,输出连个字符串的最长公共子序列。如过最长公共子序列为空,则输出-1。
输入: "1A2C3D4B56","B1D23CA45B6A"
输出:"123456"
思路:设置二维数组dp[i][j],其中 i 和 j 表示从str1的第 i 位到str2的第 j 位中的最长公共子序列
import java.util.*;
public class Solution {
/**
* longest common subsequence
* @param s1 string字符串 the string
* @param s2 string字符串 the string
* @return string字符串
*/
public String LCS (String s1, String s2) {
// write code here
int n1 = s1.length(), n2 = s2.length();
//默认赋值,[0][?],[?][0]默认两侧皆0,类似公式中0的场景
int[][] dp = new int[n1 + 1][n2 + 1];
for(int i = 1; i <= n1; i++)
for(int j = 1; j <= n2; j++){
if(s1.charAt(i - 1) == s2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1] + 1;
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
//反推结果
int i = n1, j = n2;
StringBuffer sb = new StringBuffer();
while(i > 0 && j > 0){
//反推公式中不相等的场景
//根据之前的公式,知道两条字符串的下标都前进一位
if(s1.charAt(i - 1) == s2.charAt(j - 1)){
sb.append(s1.charAt(i - 1));
i--;
j--;
}else {
//对应公式中不相等的反推场景
if(dp[i][j - 1] > dp[i - 1][j]){
//找大的那个方向,此处是左边大于上面,则该处的结果是来自左边
j--;
j--;
}else if(dp[i][j - 1] < dp[i - 1][j]){
i--;
}else if(dp[i][j - 1] == dp[i - 1][j]){
j--;
}
}
}
//由于是从后往前加入字符的,需要反转才能得到正确结果
return sb.reverse().toString();
}
}
1.2 最长上升子序列(leetcode 300)
题目:给定数组arr,设长度为n,输出arr的最长上升子序列的长度
输入:[2,1,5,3,6,4,8,9,7]
输出:5
思路:设置并计算dp[i],i 表示从 0 到 i 中当前最长上升数值的个数
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums == null || nums.length == 0)
return 0;
int[] dp = new int[nums.length];
dp[0] = 1;
int res = 1;
for(int i = 1; i < dp.length; i++){
int maxval = 0;
for(int j = 0; j < i; j++){
if(nums[i] > nums[j]){
maxval = Math.max(maxval, dp[j]);
}
}
dp[i] = maxval + 1;
res = Math.max(dp[i], res);
}
return res;
}
}
1.3 最长回文子串(牛客版,leetcode 5)
题目:给定字符串A以及它的长度n,请返回最长回文子串的长度。
输入:"abc1234321ab",12
输出:7
思路:设置二维数组dp[i][j], 表示第 i 个字符到第 j 个字符是否是回文串
import java.util.*;
public class Palindrome {
public int getLongestPalindrome(String A, int n) {
// write code here
if(A == null || n <= 0) return 0;
// 第 i 个字符到第 j 个字符是否是回文串
boolean[][] dp = new boolean[n][n];
int maxLen = 0;
// 字符串收尾字母长度差 len = j - i
for(int len = 0; len < n; len++){
// 字符串起始位置 i
for(int i = 0; i + len < n; i++){
// 字符串终止位置 j
int j = i + len;
if(len == 0){
dp[i][j] = true;
}else if(len == 1){
dp[i][j] = A.charAt(i) == A.charAt(j);
}else{
dp[i][j] = (A.charAt(i) == A.charAt(j) && dp[i + 1][j - 1]);
}
if(dp[i][j] && j - i + 1 > maxLen){
maxLen = j - i + 1;
}
}
}
return maxLen;
}
}
1.4 接雨水
对于每一列来说,他能存的雨水量是他 左边最高墙和右边最高墙中较低的那堵墙的高度减去自身墙的高度 。所以可以用数组记录每列左右最高墙的高度,然后计算每一列可以存的雨水量。
class Solution {
public int trap(int[] height) {
int sum = 0;
int[] max_left = new int[height.length];
int[] max_right = new int[height.length];
for(int i = 1; i < height.length - 1; i++){
max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
}
for(int i = height.length - 2; i >= 0; i--){
max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
}
for(int i = 1; i < height.length - 1; i++){
int min = Math.min(max_left[i], max_right[i]);
if(min > height[i]){
sum += min - height[i];
}
}
return sum;
}
}
1.5 重复数字的所有排列(回溯)
题目:给出一组数字,返回该组数字的所有排列
示例:
[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].
回溯核心思想:采用递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」
1、路径:已经做出的选择
2、选择列表:当前可以做的选择
3、结束条件:到达决策树底层,无法再做选择的条件
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
ArrayList<ArrayList<Integer>> res;
public ArrayList<ArrayList<Integer>> permute(int[] num) {
res = new ArrayList<ArrayList<Integer>>();
if(num == null || num.length == 0) return res;
//对数组元素进行从小到大排序
Arrays.sort(num);
ArrayList<Integer> list = new ArrayList<Integer>();
solve(list, num);
return res;
}
private void solve(ArrayList<Integer> list, int[] num){
if(list.size() == num.length){
res.add(new ArrayList<Integer>(list));
return;
}
for(int i = 0; i < num.length; i++){
if(!list.contains(num[i])){
list.add(num[i]);
solve(list, num);
list.remove(list.size() - 1);
}
}
}
}
1.6 集合的所有子集 (牛客版,leetcode 78)
2. 树
2.1 判断一颗二叉树是否为二叉搜索树和完全二叉树
- 二叉搜索树:每个节点左边节点小于右边节点,左子树的最大值一定小于根节点,小于右子树的最大值;通过中序遍历,严格递增。
- 完全二叉树:层序遍历,除了最后的一层,每层都是满的。采用层序遍历,一旦出现null,则队列中剩余的节点必须为叶子节点。
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类 the root
* @return bool布尔型一维数组
*/
public boolean[] judgeIt (TreeNode root) {
// write code here
// 两个情况分别判断:
// 二叉搜索树:每个节点左边节点小于右边节点,左子树的最大值一定小于根节点,小于右子树的最大值;通过中序遍历,严格递增
// 完全二叉树:层序遍历,除了最后的一层,每层都是满的
if(root == null) return new boolean[]{false, false};
boolean b1 = isBST(root);
boolean b2 = isCBT(root);
return new boolean[]{b1, b2};
}
private boolean isBST(TreeNode root){
List<Integer> list = new ArrayList<>();
midSearch(root, list);
for(int i = 0; i < list.size() - 1; i++){
if(list.get(i) > list.get(i + 1))
return false;
}
return true;
}
private void midSearch(TreeNode root, List<Integer> list){
if(root == null) return;
midSearch(root.left, list);
list.add(root.val);
midSearch(root.right, list);
}
private boolean isCBT(TreeNode root){
// 判断一棵树是否为完全二叉树,层序遍历,一旦出现null,则队列中剩余的节点必须为叶子节点
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode cur = queue.remove();
if(cur.left == null){
if(cur.right != null) return false;
while(!queue.isEmpty()){
TreeNode node = queue.remove();
if(node.left != null ||node.right != null) return false;
}
}else{
queue.add(cur.left);
}
if(cur.right == null){
while(!queue.isEmpty()){
TreeNode node = queue.remove();
if(node.left != null ||node.right != null) return false;
}
}else{
queue.add(cur.right);
}
}
return true;
}
}
2. 2 二叉树的最近公共祖先(leetcode 236)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left == null) return right;
if(right == null) return left;
if(right != null && left != null) return root;
return null;
}
}
2.3 二叉搜索树的第k小节点(牛客版)
public class Solution {
TreeNode res;
int k;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot == null || k <= 0) return null;
this.k = k;
postOrder(pRoot);
return res;
}
private void postOrder(TreeNode pRoot){
if(pRoot == null) return;
postOrder(pRoot.left);
if(k == 0) return;
if(--k == 0) res = pRoot;
postOrder(pRoot.right);
}
}
2.4 二叉搜索树与双向链表(牛客版)
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
public class Solution
{
public TreeNode Convert(TreeNode root)
{
if(root==null)return null;
if(root.left==null&&root.right==null)return root;
TreeNode left=Convert(root.left);
TreeNode p=left;
while(p!=null&&p.right!=null)
{
p=p.right;
}
if(left!=null)
{
p.right=root;
root.left=p;
}
TreeNode right=Convert(root.right);
if(right!=null)
{
root.right=right;
right.left=root;
}
return left!=null?left:root;
}
}
2.5 重建二叉树(剑指offer,leetcode版)
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
int length = preorder.length;
for (int i = 0; i < length; i++) {
indexMap.put(inorder[i], i);
}
TreeNode root = buildTree(preorder, 0, length - 1, inorder, 0, length - 1, indexMap);
return root;
}
public TreeNode buildTree(int[] preorder, int preorderStart, int preorderEnd, int[] inorder, int inorderStart, int inorderEnd, Map<Integer, Integer> indexMap) {
if (preorderStart > preorderEnd) {
return null;
}
int rootVal = preorder[preorderStart];
TreeNode root = new TreeNode(rootVal);
if (preorderStart == preorderEnd) {
return root;
} else {
int rootIndex = indexMap.get(rootVal);
int leftNodes = rootIndex - inorderStart, rightNodes = inorderEnd - rootIndex;
TreeNode leftSubtree = buildTree(preorder, preorderStart + 1, preorderStart + leftNodes, inorder, inorderStart, rootIndex - 1, indexMap);
TreeNode rightSubtree = buildTree(preorder, preorderEnd - rightNodes + 1, preorderEnd, inorder, rootIndex + 1, inorderEnd, indexMap);
root.left = leftSubtree;
root.right = rightSubtree;
return root;
}
}
}
3. 数组
3.1 数组中的最长连续子序列(牛客版)
题:给定无序数组arr,返回其中最长的连续序列的长度(要求值连续,位置可以不连续,例如 3,4,5,6为连续的自然数)
例:输入:[100,4,200,1,3,2] 输出:4
- 方法一:排序,然后再遍历一遍找最长连续递增数列,时间复杂度O(NlogN)
- 方法二:使用HashMap或者HashSet,将数组元素存入set中,然后遍历set,先找到每个序列的开头元素
import java.util.*;
public class Solution {
public int MLS (int[] arr) {
// write code here
if(arr == null || arr.length == 0) return 0;
HashSet<Integer> set = new HashSet<>();
int maxLen = 0;
for(int value : arr){
set.add(value);
}
for(int num : set){
if(!set.contains(num - 1)){
int currentNum = num;
int currentLen = 1;
while(set.contains(currentNum + 1)){
currentNum += 1;
currentLen++;
}
maxLen = Math.max(maxLen, currentLen);
}
}
return maxLen;
}
}
3.2 寻找第k大的数(牛客版)
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数
输入:[1,3,5,2,2],5,3
输出:2
思路:快速排序 + 递归(注意第 k 大是从后往前数,第 k 小是从小往大)
import java.util.*;
public class Finder {
public int findKth(int[] a, int n, int K) {
// write code here
return findK(a, 0, n - 1, K);
}
private int findK(int[] arr, int left, int right, int k){
if(left <= right){
int temp = quickSort(arr, left, right);
if(temp == arr.length - k){
return arr[temp];
}else if(temp > arr.length - k){
return findK(arr, left, temp - 1, k);
}else{
return findK(arr, temp + 1, right, k);
}
}
return -1;
}
private int quickSort(int[] a, int left, int right){
int key = a[left];
while(left < right){
while(left < right && a[right] >= key){
right--;
}
a[left] = a[right];
while(left < right && a[left] <= key){
left++;
}
a[right] = a[left];
}
a[left] = key;
return left;
}
}
3.3 旋转数组的最小数字
题目:输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例子:数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。建议使用O(logn)时间复杂度
思想:二分查找的变种,注意旋转后的边界
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length == 0 || array == null)
return 0;
int left = 0;
int right = array.length - 1;
int mid = left;
while(array[left] >= array[right]){
// 二分查找的边界
if(right - left == 1){
mid = right;
break;
}
mid = (left + right) / 2;
if(array[mid] >= array[left])
left = mid;
else if(array[mid] <= array[right])
right = mid;
}
return array[mid];
}
}
3.4 螺旋数组(牛客版)
题目:给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。
例如:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
返回:[1,2,3,6,9,8,7,4,5]
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(matrix.length == 0) return res;
int rowBegin = 0;
int rowEnd = matrix.length - 1;
int colBegin = 0;
int colEnd = matrix[0].length - 1;
while(rowBegin <= rowEnd && colBegin <= colEnd){
for(int j = colBegin; j <= colEnd; j++){
res.add(matrix[rowBegin][j]);
}
rowBegin++;
for(int i = rowBegin; i <= rowEnd; i++){
res.add(matrix[i][colEnd]);
}
colEnd--;
if(rowBegin <= rowEnd){
for(int j = colEnd; j >= colBegin; j--){
res.add(matrix[rowEnd][j]);
}
}
rowEnd--;
if(colBegin <= colEnd){
for(int i = rowEnd; i >= rowBegin; i--){
res.add(matrix[i][colBegin]);
}
}
colBegin++;
}
return res;
}
}
3.5 字符串最长无重复子串(牛客版,leetcode 48)
题目:给定一个数组arr,返回arr的最长无的重复子串的长度(无重复指的是所有数字都不相同)。
解法:使用hashmap来保存重复字符的位置。
import java.util.*;
public class Solution {
public int maxLength (int[] arr) {
// write code here
HashMap<Integer, Integer> map = new HashMap<>();
int max = 1;
for(int start = 0, end = 0; end < arr.length; end++){
if(map.containsKey(arr[end])){
start = Math.max(start, map.get(arr[end]) + 1);
}
max = Math.max(max, end - start + 1);
map.put(arr[end], end);
}
return max;
}
}
4. 链表
4.1 链表中的节点每k个一组翻转(牛客版)
public class Solution {
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
if(head == null || head.next == null || k < 2)
return head;
ListNode res = new ListNode(-1);
res.next = head;
ListNode pre = res;
ListNode end = res;
while(end != null){
for(int i = 0; i < k && end != null; i++){
end = end.next;
}
if(end == null) break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
pre.next = reverseList(start);
start.next = next;
pre = start;
end = pre;
}
return res.next;
}
private ListNode reverseList(ListNode head){
ListNode preNode = null, pNode = head;
while(pNode != null){
ListNode pNext = pNode.next;
pNode.next = preNode;
preNode = pNode;
pNode = pNext;
}
return preNode;
}
}
4.2 删除排序链表中的重复元素(牛客版,leetcode 82)
题目:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
给出的链表为1→2→3→3→4→4→5, 返回 1→2→5.
给出的链表为1→1→1→2→3, 返回 2→3.
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head == null || head.next == null)
return head;
ListNode res = new ListNode(-1);
res.next = head;
ListNode preNode = res;
ListNode pNode = head;
while(pNode != null && pNode.next != null){
if(preNode.next.val != pNode.next.val){
preNode = preNode.next;
pNode = pNode.next;
}else{
while(pNode != null && pNode.next != null && preNode.next.val == pNode.next.val){
pNode = pNode.next;
}
preNode.next = pNode.next;
pNode = pNode.next;
}
}
return res.next;
}
}
4.3 两个链表相加
给定两个这种链表,请生成代表两个整数相加值的结果链表。
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
思路:先反转,再相加,注意进位以及最后一次相加(String类型的大数加法思路一致)
import java.util.*;
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
if(head1 == null) return head2;
if(head2 == null) return head1;
ListNode l1 = reverseList(head1);
ListNode l2 = reverseList(head2);
int carry = 0;
ListNode res = new ListNode(-1);
ListNode curNode = res;
while(l1 != null || l2 != null){
int num1 = l1 == null ? 0 : l1.val;
int num2 = l2 == null ? 0 : l2.val;
int val = (num1 + num2 + carry) % 10;
carry = (num1 + num2 + carry) / 10;
curNode.next = new ListNode(val);
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
curNode = curNode.next;
}
return reverseList(res.next);
}
public ListNode reverseList(ListNode head){
ListNode preNode = null;
ListNode curNode = head;
while(curNode != null){
ListNode nextNode = curNode.next;
curNode.next = preNode;
preNode = curNode;
curNode = nextNode;
}
return preNode;
}
}
4.4 合并k个升序链表(leetcode 23)
题目:合并 k 个已排序的链表并将其作为一个已排序的链表返回。
思路:归并排序法 + 两个链表合并法
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null || lists.length == 0) return null;
return merge(lists, 0, lists.length - 1);
}
private ListNode merge(ListNode[] lists, int left, int right){
if(left == right) return lists[left];
int mid = left + (right - left) / 2;
ListNode l1 = merge(lists, left, mid);
ListNode l2 = merge(lists, mid + 1, right);
return mergeTwoLists(l1, l2);
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2){
ListNode res = new ListNode(-1);
ListNode pNode = res;
while(l1 != null && l2 != null){
if(l1.val <= l2.val){
pNode.next = l1;
l1 = l1.next;
}else{
pNode.next = l2;
l2 = l2.next;
}
pNode = pNode.next;
}
pNode.next = l1 == null ? l2 : l1;
return res.next;
}
}
4.5 LRU
class LRUCache {
class Node {
public int key, val;
public Node prev, next;
public Node(int key, int val){
this.key = key;
this.val = val;
}
}
class DoubleList{
private Node head, tail;
private int size;
public DoubleList(){
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
size = 0;
}
public void addFirst(Node x){
x.next = head.next;
x.prev = head;
head.next.prev = x;
head.next = x;
size++;
}
public void remove(Node x){
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
}
public Node removeLast(){
if(tail.prev == head){
return null;
}
Node last = tail.prev;
remove(last);
return last;
}
public int size(){
return this.size;
}
}
public HashMap<Integer, Node> map;
private DoubleList cache;
private int cap;
public LRUCache(int capacity) {
map = new HashMap<>();
cache = new DoubleList();
this.cap = capacity;
}
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
int val = map.get(key).val;
put(key, val);
return val;
}
public void put(int key, int value) {
Node x = new Node(key, value);
if(map.containsKey(key)){
cache.remove(map.get(key));
cache.addFirst(x);
map.put(key, x);
}else{
if(cache.size == cap){
Node last = cache.removeLast();
map.remove(last.key);
}
cache.addFirst(x);
map.put(key, x);
}
}
}
数学问题
1. 给定一个 0-4随机数生成器 如何生成0-6随机数(leetcode 470)
定理:已知 rand_N() 可以等概率的生成[1, N]范围的随机数。那么:
(rand_X() - 1) × Y + rand_Y() ==> 可以等概率的生成[1, X * Y]范围的随机数,即实现了 rand_XY()
例:(rand9() - 1) × 7 + rand7() = rand63();
class Solution extends SolBase {
public int rand10() {
while(true) {
int a = rand7();
int b = rand7();
int num = (a-1)*7 + b; // rand 49
if(num <= 40) return num % 10 + 1; // 拒绝采样
a = num - 40; // rand 9
b = rand7();
num = (a-1)*7 + b; // rand 63
if(num <= 60) return num % 10 + 1;
a = num - 60; // rand 3
b = rand7();
num = (a-1)*7 + b; // rand 21
if(num <= 20) return num % 10 + 1;
}
}
}
2. 求平方根