leetcode

哈希

HashMap

遍历:

// 遍历键值对
for (Map.Entry<Integer, String> entry : map.entrySet()) {
    int key = entry.getKey();
    String value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}

// 遍历键
for (int key : map.keySet()) {
    System.out.println("Key: " + key);
}

// 遍历值
for (String value : map.values()) {
    System.out.println("Value: " + value);
}

常用方法

  • containsKey(Object key) 方法用于检查 HashMap 中是否包含指定的键。
  • getOrDefault(Object key, V defaultValue) 方法用于获取 HashMap 中指定键的值,如果键不存在,则返回默认值。
  • values() 方法返回一个 Collection 视图,其中包含 HashMap 中所有的值。
  • size()
  • putIfAbsent()用于在仅当指定键尚不存在时才将给定键值对插入映射中

key

● 排序后的字符串唯一,可以作为键
● 返回不同连续子数组时,可以用数组下标

HashSet

remove()
contains()

双指针

  • 同一个起点
  • 左右两端
  • 慢指针一次走一步,快指针一次走两步,快慢指针同时出发。
    • 当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
    • 具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。

链表

滑动窗口

动态规划

字符串

  • toCharArray() 方法,可以将字符串转换为字符数组
  • substring(int beginIndex):从指定的索引beginIndex处开始截取到字符串末尾,返回一个新的字符串。
  • substring(int beginIndex, int endIndex):从指定的起始索引beginIndex开始,到结束索引endIndex(但不包括该位置)截取字符串,返回一个新的字符串。
  • 将一个字符转换为字符串,可以使用
    • String.valueOf()方法
    • 将字符与空字符串相加
  • Character
    • Character.isDigit()判断是否是数字
    • Character.isLetter() 判断是否是字符

StringBuilder

StringBuilder 是 Java 中的一个类,用于创建和操作可变字符串。与 String 不同,String 是不可变的,每次修改都会生成新的字符串,而 StringBuilder 允许你在原有内容上进行修改而不创建新的对象。

常用方法

StringBuilder sb = new StringBuilder();

// 1. append - 添加字符串
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // 输出: Hello World

// 2. insert - 在指定位置插入字符串
sb.insert(5, ",");
System.out.println(sb.toString()); // 输出: Hello, World

// 3. delete - 删除指定范围的字符
sb.delete(5, 6);
System.out.println(sb.toString()); // 输出: Hello World
sb.deleteCharAt(sb.length() - 1);

// 4. reverse - 反转字符串
sb.reverse();
System.out.println(sb.toString()); // 输出: dlroW olleH

// 5. capacity - 获取当前容量
System.out.println("Capacity: " + sb.capacity());

// 6. length - 获取当前长度
System.out.println("Length: " + sb.length());



数组

  • Arrays.sort():对于基本数据类型的数组(如 int[]、double[]、char[] 等),可以直接使用 Arrays 类的 sort 方法来排序。在原数组上排序
    • 实现自定义排序:
      Comparator如何判断升降序:用来比较的元素类型为int[ ],compare函数的含义为是否交换两个元素的值,如果interval1[0]>interval2[0],返回true,即交换两个元素的值。所以,经过排序后的数组为按元素索引0处的值升序排列。
Arrays.sort(intervals, new Comparator<int[]>() {
	public int compare(int[] interval1, int[] interval2) {
	    return interval1[0] - interval2[0];
	}
});

● char[ ]数组转字符串:new String()
Arrays.asList()
Arrays.copyOfRange()

List

  • 将List<int []>转二维数组返回:
    return result.toArray(new int[result.size()][]);
  • addAll():将另一个集合中的所有元素添加到当前List中的方法
  • List<List<Integer>> list = new ArrayList<>();
  • ArrayList<String> list = new ArrayList<>(10);初始化容量为10
  • list.add(Arrays.asList(1, 2));
  • get():获取List中元素
  • add(int index, E element)方法在指定位置插入元素,第一个参数是一个整数,表示要插入元素的位置,不可以传变量。

  1. 初始化栈: Stack<Integer> stack = newStack<>();
    2. push(E item): 将元素压入栈顶。
  2. pop(): 弹出栈顶元素,并返回该元素。
  3. peek(): 返回栈顶元素,但不移除它。
  4. empty(): 检查栈是否为空。
    6. search(Object o): 查找元素在栈中的位置,返回距离栈顶最近的位置(1为栈顶)。
  5. isEmpty(): 判断栈是否为空,等同于empty()方法。

队列

双端队列Deque

  • Deque<String> deque =new ArrayDeque<>();
  • 在头部和尾部添加元素
    • deque.addFirst("first");
    • deque.addLast("last");
  • 获取并移除头部和尾部元素
    • String first = deque.removeFirst();
    • String last = deque.removeLast();
  • 获取但不移除头部和尾部元素
    • String peekFirst = deque.peekFirst();
    • String peekLast = deque.peekLast();

排序

回溯

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

组合是不强调元素顺序的,排列是强调元素顺序
{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。
集合的大小就构成了树的宽度,递归的深度就构成了树的深度

算法模板

for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,一般来说,搜索叶子节点就是找的其中一个结果

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

以上,处理节点不一定是在for()循环中,如括号生成就是用的两个if()

排列

组合

  • 如果是一个集合来求组合的话,就需要startIndex
  • 如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex
    startIndex :防止出现重复的组合,根据条件传参数时要不要i + 1
  • 传参 i:在每次遍历完后不能取上次的值,即list中元素可以重复,但是list要去重
  • 传参 i + 1:取完上个值后就不能取这个值了,即list中元素不可以重复

二叉树

中序遍历

先序遍历

后序遍历

  • 适合自底向上遍历,天然回溯

二叉搜索树

二叉搜索树的中序遍历递增序列,可以用于;

  • 寻找树中第几大的元素
  • 判断是否是二叉搜索树

递归函数什么时候要有返回值,什么时候没有返回值

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。

BFS

可以用来求最短路径问题,需要使用队列

有向无环图

把一个 有向无环图 转成 线性的排序 就叫 拓扑排序

  • 入度为 0 的节点入队列
  • 然后逐个出列,减小相关节点的入度。
  • 如果相关节点的入度新变为 0,安排它入列、再出列……直到没有入度为 0 的课可入列。

总结:拓扑排序问题

  1. 根据依赖关系,构建Map、入度数组。
  2. 选取入度为 0 的数据,根据邻接表,减小依赖它的数据的入度。
  3. 找出入度变为 0 的数据,重复第 2 步。
  4. 直至所有数据的入度为 0,得到排序,如果还有数据的入度不为 0,说明图中存在环。

DFS

动态规划

动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化。

解题步骤

  1. 定义子问题
    • 子问题是和原问题相似,但规模较小的问题。例如这道小偷问题,原问题是 “从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是 “从 k 个房子中能偷到的最大金额 ”,用 f(k) 表示。
    • 可以看到,子问题是参数化的,我们定义的子问题中有参数 k。假设一共有 n 个房子的话,就一共有 n 个子问题。动态规划实际上就是通过求这一堆子问题的解,来求出原问题的解。这要求子问题需要具备两个性质:
      • 原问题要能由子问题表示。例如这道小偷问题中,k=n 时实际上就是原问题。
      • 一个子问题的解要能通过其他子问题的解求出。例如这道小偷问题中,f(k) 可以由 f(k−1) 和 f(k−2) 求出
  2. 写出子问题的递推关系
  3. 确定 DP 数组的计算顺序
  4. 空间优化(可选)

背包问题

遍历顺序就记住遍历物品在外层循环,遍历背包容量在内层循环

01背包

每个物品只有选与不选两个状态,最多选一个

二维数组[i][j]

遍历顺序无所谓,因为dp递归公式有i-1,所以i为0的时候就一定要初始化。递归公式中是由上一行的上方和左上角得到的,遍历顺序不会影响
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

滚动数组

从大到小遍历背包容量,保证物品只加一次。
一维数组初始化为0
先遍历物品嵌套遍历背包容量,不可以先遍历背包容量嵌套遍历物品。因为一维dp的写法,背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。就比如说:
weight = [1, 3, 4], value = [15, 20, 30]
j=4:

  • i = 0, dp[4] = Math.max(dp[4], dp[3] + 15) = 15,加了一个物品1
  • i = 1, dp[4] = Math.max(dp[4], dp[1] + 20) = 20,加了一个物品2,但此时的容量为4,可以放下一个物品1和一个物品2,最大价值为35
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

完全背包

不选/选几个
从小到大遍历背包容量,因为完全背包物品可以重复加
在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!但还要再加个判断,因为先遍历j,在遍历i,要判断j >= weight[i],再做大小比较
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

// 先遍历背包,再遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
    cout << endl;
}
求排列

先遍历背包再遍历物品,排列数,先把物品排列好再放进背包

求组合

先遍历物品再遍历背包,组合数就是把物品放进背包,不考虑放的顺序

贪心

common

Math.max()超时

第二种写法使用Math.max()会超出时间限制,猜测是因为对于下面测试用例来说第一种只比较没有赋值,所以速度较快

第一种写法为:
for (int j = i - 1; j >= 0; j--) {
    if (height[j] > max_left) {
        max_left = height[j];
    }
}
第二种写法为:
for (int left = i - 1; left >= 0; left--){
    leftMax = Math.max(leftMax, height[left]);
}

测试用例为:
在这里插入图片描述

数据转换

  • char -> String : String.valueOf()
  • String -> int : Integer.parseInt()
  • Character -> int :
//第一种方法:如果字符是数字字符('0' - '9'),你可以直接使用 Character.digit(char, int radix) 方法
char ch = '5';
int intValue = Character.digit(ch, 10); // intValue = 5
//你可以将字符减去字符 '0',因为 '0' 的 ASCII 码值为 48。
char ch = '5';
int intValue = ch - '0'; // intValue = 5
//使用 (int) ch 强制类型转换。
char ch = 'A';
int unicodeValue = (int) ch; // unicodeValue = 65
char cur = s.charAt(ptr);
if (Character.isDigit(cur)) {
    // 获取一个数字并进栈
    String digits = getDigits(s);
    stk.addLast(digits);
}

  • List -> int
  • int -> Integer : Integer.valueOf()
  • Integer -> int :
Integer numObj = new Integer(10);
int num = numObj; // 自动拆箱
int num2 = numObj.intValue(); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值