ivan的刷题笔记
LeetCode刷题笔记
1.链表
1.1按链表从尾到头的顺序返回每个节点的值
package com.bing.从尾到头打印链表;
import java.util.ArrayList;
-
描述
-
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
-
如输入{1,2,3}的链表如下图:
-
返回一个数组为[3,2,1]
-
输入:
-
{1,2,3}
-
复制
-
返回值:
-
[3,2,1]
-
复制
-
示例2
-
输入:
-
{67,0,24,58}
-
复制
-
返回值:
-
[58,24,0,67]
-
解题思路:
-
先将链表进行反转,之后再遍历
*/
public class Main {
public static void main(String[] args) {
ListNode l1 = new ListNode(1);
l1.next=new ListNode(2);
l1.next.next=new ListNode(3);
System.out.println(ListNode.print(l1));
ListNode.printNode(l1);
System.out.println(printListFromTailToHead(l1));
}
public static class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
public static void printNode(ListNode node){
while (node!=null){
System.out.println( node.val);
node=node.next;
}
}
public static ArrayList<Integer> print(ListNode node){
ArrayList<Integer> list = new ArrayList<>();
while (node!=null){
list.add(node.val);
node=node.next;
}
return list;
}
}
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode pre=null;
ListNode cur=listNode;
ListNode temp=cur;
while (cur!=null){
temp=cur.next;
cur.next=pre;
pre= cur;
cur=temp;
}
while (pre!=null){
list.add(pre.val);
pre=pre.next;
}
return list;
}
}
2.贪心算法
3.动态规划
3.1最小花费爬楼梯
package com.bing.最小花费爬楼梯;
/**
- 描述
- 给定一个整数数组 cost \cost ,其中 cost[i]\cost[i] 是从楼梯第i \i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
- 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
- 请你计算并返回达到楼梯顶部的最低花费。
- 示例1
- 输入:
- [2,5,20]
- 返回值:
- 5
- 说明:
- 你将从下标为1的台阶开始,支付5 ,向上爬两个台阶,到达楼梯顶部。总花费为5
- 示例2
- 输入:
- [1,100,1,1,1,90,1,1,80,1]
- 返回值:
- 6
- 复制
- 说明:
- 你将从下标为 0 的台阶开始。
- 1.支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 2.支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 3.支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 4.支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 5.支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 6.支付 1 ,向上爬一个台阶,到达楼梯顶部。
- 总花费为 6 。
- 解题思想:动态规划
- step 1:可以用一个数组记录每次爬到第i阶楼梯的最小花费,然后每增加一级台阶就转移一次状态,最终得到结果。
- step 2:(初始状态) 因为可以直接从第0级或是第1级台阶开始,因此这两级的花费都直接为0.
- step 3:(状态转移) 每次到一个台阶,只有两种情况,要么是它前一级台阶向上一步,要么是它前两级的台阶向上两步,
- 因为在前面的台阶花费我们都得到了,因此每次更新最小值即可,
- 转移方程为:dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])dp[i]
- = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])dp[i]
- =min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])。
*/
public class Main {
public static void main(String[] args) {
int[] cost={1,100,1,1,1,90,1,1,80,1};
System.out.println(minCostClimbingStairs(cost));
}
public static int minCostClimbingStairs (int[] cost) {
// write code here
// [1,100,1,1,1,90,1,1,80,1]
// [2,5,20]
int[] dp=new int[cost.length+1];
for (int i=2;i<cost.length+1;i++){
dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.length];
}
}
3.2把数字翻译成字符串
package com.bing.把数字翻译成字符串;
/**.
- 描述
- 有一种将字母编码成数字的方式:‘a’->1, ‘b->2’, … , ‘z->26’。
- 我们把一个字符串编码成一串数字,再考虑逆向编译成字符串。
- 由于没有分隔符,数字编码成字母可能有多种编译结果,例如 11 既可以看做是两个 ‘a’ 也可以看做是一个 ‘k’ 。但 10 只可能是 ‘j’ ,因为 0 不能编译成任何结果。
- 现在给一串数字,返回有多少种可能的译码结果
- 示例1
- 输入:
- “12”
- 返回值:
- 2
- 说明:
- 2种可能的译码结果(”ab” 或”l”)
- 示例2
- 输入:
- “31717126241541717”
- 返回值:
- 192
- 复制
- 说明:
- 192种可能的译码结果
- 解题思路:
- 对于普通数组1-9,译码方式只有一种,
- 但是对于11-19,21-26,译码方式有可选择的两种方案,因此我们使用动态规划将两种方案累计。
- step 1:用辅助数组dp表示前i个数的译码方法有多少种。
- step 2:对于一个数,我们可以直接译码它,也可以将其与前面的1或者2组合起来译码:如果直接译码,则dp[i]=dp[i−1];如果组合译码,则dp[i]=dp[i−2]
- step 3:对于只有一种译码方式的,选上种dp[i−1]即可,对于满足两种译码方式(10,20不能)则是dp[i−1]+dp[i−2]
- step 4:依次相加,最后的dp[length]即为所求答案。
*/
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String numbers="12";
System.out.println(solve(numbers));
}
public static int solve (String nums) {
//12
//排除0
if (nums.equals("0")){
return 0;
}
//排除只有一种可能的10 和 20
if (nums==("10")|| nums==("20")){
return 1;
}
//当0的前面不是1或2时,无法译码,0种
for (int i=1;i<nums.length();i++){
if (nums.charAt(i)=='0'){
if (nums.charAt(i-1)!='1'&& nums.charAt(i-1)!='2'){
return 0;
}
}
}
int[] dp=new int[nums.length()+1];
//辅助数组初始化为1
Arrays.fill(dp,1);
//在11-19,21-26之间的情况
for (int i=2;i<=nums.length();i++){
if (nums.charAt(i-2)=='1'&& nums.charAt(i-1)!='0'||nums.charAt(i-2)=='2' && nums.charAt(i - 1) > '0' && nums.charAt(i - 1) < '7'){
dp[i]=dp[i-1]+dp[i-2];
}else {
dp[i]=dp[i-1];
}
}
return dp[nums.length()];
}
}
3.3单词转换最小操作数
package com.bing.单词转换最小操作数;
/**
-
示例 1:
-
输入: word1 = “horse”, word2 = “ros”
-
输出: 3
-
解释:
-
horse -> rorse (将 ‘h’ 替换为 ‘r’)
-
rorse -> rose (删除 ‘r’)
-
rose -> ros (删除 ‘e’)
-
解题思路:
-
步骤一、定义数组元素的含义
-
步骤二:找出关系数组元素间的关系式
-
(1)、如果把字符 word1[i] 替换成与 word2[j] 相等,则有 dp[i] [j] = dp[i-1] [j-1] + 1; (2)、如果在字符串 word1末尾插入一个与 word2[j] 相等的字符,则有 dp[i] [j] = dp[i] [j-1] + 1; (3)、如果把字符 word1[i] 删除,则有 dp[i] [j] = dp[i-1] [j] + 1;
-
步骤三、找出初始值
*/
public class Main {
public static void main(String[] args) {
System.out.println(minDistance("horse", "ros"));
}
/*
horse 1
ros 2
*/
public static int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
word1 = ' ' + word1; //添加空格
word2 = ' ' + word2;
int[][] f = new int[n + 10][m + 10];
for(int i = 0;i <= n;i++) {
f[i][0] = i; //i次删除
}
for(int i = 0;i <= m;i++) {
f[0][i] = i; //i次删除 word1 -> word2
}
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
{
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1); //添加或者删除
if(word1.charAt(i) == word2.charAt(j)){
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
}
else{
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);//替换
}
}
return f[n][m];
}
}
3.4机器人走路路径.机器人走路最小路径和
package com.bing.机器人走路路径.机器人走路最小路径和;
/**
- 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
- 举例:
- 输入:
- arr = [
- [1,3,1],
- [1,5,1],
- [4,2,1]
- ]
- 输出: 7
- 解释: 因为路径 1→3→1→1→1 的总和最小
*/
public class Main {
public static void main(String[] args) {
int[][] dp={{1,3,1},{1,5,1},{4,2,1}};
System.out.println(uniquePaths(dp));
}
public static int uniquePaths(int[][] arr) {
int m=arr.length;
int n=arr[0].length;
if (m<0 ||n<0){
return 0;
}
int[][] dp=new int[m][n];
// 初始化
dp[0][0]=arr[0][0];
/*
[1,3,1],
* [1,5,1],
* [4,2,1]
*/
// 初始化最上边的行
for (int i=1;i<n;i++){
dp[0][i]=dp[0][i-1]+arr[0][i];
}
// 初始化最左边的列
for (int i=1;i<m;i++){
dp[i][0]=dp[i-1][0]+arr[i][0];
}
//推导出 dp[m-1][n-1]
for (int i=1;i<m;i++){
for (int j=1;j<n;j++){
dp[i][j]= Math.min(dp[i-1][j],dp[j-1][i])+arr[i][j];
}
}
return dp[m-1][n-1];
}
}
3.5台阶总共有多少种跳法
package com.bing.跳台阶;
import java.util.Arrays;
/**
*
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
- 数据范围:1 \leq n \leq 401≤n≤40
- 要求:时间复杂度:O(n)O(n) ,空间复杂度: O(1)O(1)
- 示例1
- 输入
- 2
- 输出
- 2
- 说明
- 青蛙要跳上两级台阶有两种跳法,分别是:先跳一级,再跳一级或者直接跳两级。因此答案为2
- 示例2
- 输入
- 7
- 输出
- 21
*/
public class Main {
public static void main(String[] args) {
int a=2;
System.out.println(jumpFloor(a));
}
public static int jumpFloor(int target) {
int[] dp=new int[40];
dp[0]=dp[1]=1;
for (int i=2;i<=target;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[target];
}
}
4.分治法
5.排序算法
6.查找算法
7.搜索算法
旋转数组的最小数字
package com.bing.旋转数组的最小数字;
/**
- 描述
- 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,
- 即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,
- 比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
- 示例1
- 输入:
- [3,4,5,1,2]
- 返回值:
- 1
- 示例2
- 输入:
- [3,100,200,3]
- 返回值:
- 3
- 解题思路:
- step 1:双指针指向旋转后数组的首尾,作为区间端点。
- step 2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。
- step 3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
- step 4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。
- step 5:通过调整区间最后即可锁定最小值所在。
*/
public class Main {
public static void main(String[] args) {
int[] array={3,100,1,1};
System.out.println(minNumberInRotateArray(array));
}
public static int minNumberInRotateArray(int [] array) {
//3,100,200,3
int left=0;
int right=array.length-1;
while (left<right){
int mid=(left+right)/2;
//最小的数字在mid右边
if (array[mid]>array[right]){
left=mid+1;
}
//无法判断,一个一个试
else if(array[mid]==array[right]){
right--;
}
//最小数字要么是mid要么在mid左边
else {
right=mid;
}
}
return array[left];
}
}
二维数组中的查找
package com.bing.旋转数组的最小数字.二维数组中的查找;
import javax.smartcardio.ATR;
/**
- [
- [1,2,8,9],
- [2,4,9,12],
- [4,7,10,13],
- [6,8,11,15]
- ]
- 给定 target = 7,返回 true。
- 给定 target = 3,返回 false。
- 示例1
- 输入:
- 7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
- 返回值:
- true
- 说明:
- 存在7,返回true
- 示例2
- 输入:
- 1,[[2]]
- 复制
- 返回值:
- false
*/
public class Main {
public static void main(String[] args) {
int[][] array={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
System.out.println(Find(7, array));
}
public static boolean Find(int target, int [][] array) {
/**
* [1,2,8,9],
* * [2,4,9,12],
* * [4,7,10,13],
* * [6,8,11,15]
*/
int m=array.length;
int n=array[0].length;
if (m==0|| n==0){
return false;
}
for (int i=m-1,j=0;i>=0&& j<n;){
if (array[i][j]< target){
j++;
}
else if (array[i][j]>target){
i--;
}else {
return true;
}
}
return false;
}
}
8.字符串
9.双指针
10.妙用数据结构
11.设计LRU(最近最少使用)缓存结构
/**
- 描述
- 设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 k ,并有如下两个功能
-
- set(key, value):将记录(key, value)插入该结构
-
- get(key):返回key对应的value值
- 具体的,我们使用哈希表来存储「键值对」,键值对的键作为哈希表的 Key,而哈希表的 Value 则使用我们自己封装的 Node 类,Node 同时作为双向链表的节点。
- 插入:检查当前键值对是否已经存在于哈希表:
- 如果存在,则更新键值对,并将当前键值对所对应的 Node 节点调整到链表头部(refresh 操作)
- 如果不存在,则检查哈希表容量是否已经达到容量:
- 没达到容量:插入哈希表,并将当前键值对所对应的 Node 节点调整到链表头部(refresh 操作)
- 已达到容量:先从链表尾部找到待删除元素进行删除(delete 操作),然后再插入哈希表,并将当前键值对所对应的 Node 节点调整到链表头部(refresh 操作)
- 查询:如果没在哈希表中找到该 Key,直接返回 ;如果存在该 Key,则将对应的值返回,并将当前键值对所对应的 Node 节点调整到链表头部(refresh 操作)
- 一些细节:
- 为了减少双向链表左右节点的「判空」操作,我们预先建立两个「哨兵」节点 head 和 tail。
*/
package com.bing.LRU;
import java.util.*;
import java.util.Map;
class LRUCache {
class Node {
int k, v;
Node l, r;
Node(int _k, int _v) {
k = _k;
v = _v;
}
}
int n;
Node head, tail;
Map<Integer, Node> map;
public LRUCache(int capacity) {
n = capacity;
map = new HashMap<>();
head = new Node(-1, -1);
tail = new Node(-1, -1);
head.r = tail;
tail.l = head;
}
public int get(int key) {
if (map.containsKey(key)) {
Node node = map.get(key);
refresh(node);
return node.v;
}
return -1;
}
public void put(int key, int value) {
Node node = null;
if (map.containsKey(key)) {
node = map.get(key);
node.v = value;
} else {
if (map.size() == n) {
Node del = tail.l;
map.remove(del.k);
delete(del);
}
node = new Node(key, value);
map.put(key, node);
}
refresh(node);
}
// refresh 操作分两步:
// 1. 先将当前节点从双向链表中删除(如果该节点本身存在于双向链表中的话)
// 2. 将当前节点添加到双向链表头部
void refresh(Node node) {
delete(node);
node.r = head.r;
node.l = head;
head.r.l = node;
head.r = node;
}
// delete 操作:将当前节点从双向链表中移除
// 由于我们预先建立 head 和 tail 两位哨兵,因此如果 node.l 不为空,则代表了 node 本身存在于双向链表(不是新节点)
void delete(Node node) {
if (node.l != null) {
Node left = node.l;
left.r = node.r;
node.r.l = left;
}
}
}
class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LRU (int[][] operators, int k) {
List<Integer> list = new ArrayList<>();
LRUCache lru = new LRUCache(k);
for (int[] op : operators) {
int type = op[0];
if (type == 1) {
// set(k,v) 操作
lru.put(op[1], op[2]);
} else {
// get(k) 操作
list.add(lru.get(op[1]));
}
}
int n = list.size();
int[] ans = new int[n];
for (int i = 0; i < n; i++) ans[i] = list.get(i);
return ans;
}
}
12.树
12.1重建二叉树–给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
输入:
-
[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
-
返回值:
-
{1,2,3,4,#,5,6,#,7,#,#,8}
-
输入:
-
[1],[1]
-
返回值:
-
{1}
-
输入:
-
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
-
返回值:
-
{1,2,5,3,4,6,7} *
-
解题思路:
-
step 1:先根据前序遍历第一个点建立根节点。
-
step 2:然后遍历中序遍历找到根节点在数组中的位置。
-
step 3:再按照子树的节点数将两个遍历的序列分割成子数组,将子数组送入函数建立子树。
-
step 4:直到子树的序列长度为0,结束递归。
*/
public class Main {
public static void main(String[] args) {
TreeNode treeNode = new TreeNode(0);
}
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public static TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
int m=pre.length;
int n=vin.length;
if (m==0||n==0){
return null;
}
TreeNode treeNode = new TreeNode(pre[0]);
for (int i=0;i<pre.length;i++){
if (pre[0]==vin[i]){
treeNode.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(vin,0,i));
treeNode.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,m),Arrays.copyOfRange(vin,i+1,n));
}
}
return treeNode;
}
}
13.数字
13.1判断二维数组中是否含有该整数–分治
package com.bing.分治;
import org.apache.flink.runtime.highavailability.nonha.embedded.EmbeddedLeaderService;
/**
- 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列
- 都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组
- 中是否含有该整数。
- [
- [1,2,8,9],
- [2,4,9,12],
- [4,7,10,13],
- [6,8,11,15]
- ]
- 给定 target = 7,返回 true。
- 给定 target = 3,返回 false。
- 数据范围:矩阵的长宽满足 0 \le n,m \le 5000≤n,m≤500 , 矩阵中的值满足 0 \le val \le 10^90≤val≤10
- 9
- 进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n+m)O(n+m)
- 示例1
- 输入:
- 7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
- 复制
- 返回值:
- true
- 复制
- 说明:
- 存在7,返回true
- 解题思路:
- 分治思想:
- 从左下角开始,如果目标值小于右值,则向上找,如果小于上值,则向右找
*/
```clike
public class Main {
public static void main(String[] args) {
int[][] arrays={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
System.out.println(Find(112, arrays));
}
public static boolean Find(int target, int [][] array) {
if (array.length==0){
return false;
}
if (array[0].length==0){
return false;
}
int m= array.length;
int n=array[0].length;
for (int i=m-1,j=0;i>=0 && j<n;){
if (array[i][j]>target){
i--;
}else if(array[i][j]<target){
j++;
}
else
return true;
}
return false;
}
}
13.2找出重复数字
package com.bing.重复数字;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
int[] numbers={2,3,1,0,2,5,3};
System.out.println(duplicate(numbers));
}
public static int duplicate (int[] numbers) {
// write code here
HashMap<Object, Object> hashMap = new HashMap<>();
for (int i=0;i<numbers.length;i++){
if (!hashMap.containsKey(numbers[i])){
hashMap.put(numbers[i],1);
}
else {
return numbers[i];
}
}
return -1;
}
}
13.3替换空格
package com.bing.替换空格;
/***
* 请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
* 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
*
* 数据范围:0 \le len(s) \le 1000 \0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。
*
* 示例1
* 输入:
* "We Are Happy"
* 复制
* 返回值:
* "We%20Are%20Happy"
* 复制
* 示例2
* 输入:
* " "
* 复制
* 返回值:
* "%20"
*
* StringBuffer线程安全,StringBuilder线程不安全
* StringBuffer的每个方法都用synchromd修饰
* 单线程环境下使用StringBuilder,多线程环境下使用StringBuffer
*/
public class Main {
public static void main(String[] args) {
String s="We%20Are%20Happy";
System.out.println(replaceSpace(s));
}
public static String replaceSpace (String s) {
// write code here
StringBuffer stringBuffer = new StringBuffer();
for (int i=0;i<s.length();i++){
if (s.charAt(i)==' '){
stringBuffer.append("%20");
}else{
stringBuffer.append(s.charAt(i));
}
}
return stringBuffer.toString();
}
}