leetcode hot100+相关补充算法(Java版)
2. hot100
哈希
2.1 两数之和
创建一个哈希表,对于每一个 x
,我们首先查询哈希表中是否存在 target - x
,然后将 x
插入到哈希表中,即可保证不会让 x
和自己匹配。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer>map=new HashMap<>();
int []res = new int [2];
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
res[0]=map.get(target-nums[i]);
res[1]=i;
return res;
}
map.put(nums[i], i);
}
return res;
}
}
2.2 字母异位词分组
class Solution {
List<List<String>>res;
public List<List<String>> groupAnagrams(String[] strs) {
res = new ArrayList<>();
Map<String, List<String>>map=new HashMap<>();
for(String str:strs){
char[] charArray=str.toCharArray();
Arrays.sort(charArray);
String temp = new String(charArray);
List<String>list= map.getOrDefault(temp, new ArrayList<>());
list.add(str);
map.put(temp,list);
}
Set<String>set=map.keySet();
for(String str:set){
List<String>list=map.get(str);
res.add(list);
}
return res;
}
}
2.3 最长连续序列
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer>set=new HashSet<>();
for(int num:nums){
set.add(num);
}
int res =0;
for(int num:set){
int len =1;
if(set.contains(num-1)){
continue;
}
while(set.contains(num+1)){
num++;
len++;
}
res = Math.max(res, len);
}
return res;
}
}
双指针
2.4 移动零
思路
-
初始化指针:
slow
和fast
都初始化为0。slow
指针用于记录非零元素的位置,fast
指针用于遍历数组。
-
遍历数组:
- 使用
while
循环遍历数组,条件是fast
小于数组的长度。
- 使用
-
检查非零元素:
-
在每次循环中,检查
nums[fast]
是否为非零元素。如果是,则进行交换操作:-
将
nums[fast]
和nums[slow]
的值交换。 -
将
slow
指针向前移动一位。
-
-
-
继续遍历:
- 无论是否进行了交换操作,
fast
指针都向前移动一位,继续检查下一个元素。
- 无论是否进行了交换操作,
class Solution {
public void moveZeroes(int[] nums) {
int slow=0,fast=0;
while(fast<nums.length){
if(nums[fast]!=0){
int temp=nums[fast];
nums[fast]=nums[slow];
nums[slow]=temp;
slow++;
}
fast++;
}
}
}
2.5 盛水最多的容器
-
指针初始化:
left
和right
分别指向数组的两端。
-
计算面积:
- 使用
Math.max
函数更新res
,确保res
始终存储最大面积。
- 使用
-
移动指针:
-
通过比较
height[left]
和height[right]
的大小来决定移动哪个指针。 -
移动较小的那个指针,因为面积受限于较短的那条线,移动较小的指针可能会找到更高的线,从而增加面积。
-
-
返回结果:
- 最终返回
res
,即最大面积。
- 最终返回
class Solution {
public int maxArea(int[] height) {
int res =0, len = height.length;
int left =0, right = len-1;
while(left<=right){
if(height[left]<height[right]){
res=Math.max(res, height[left]*(right-left));
left++;
}else{
res=Math.max(res, height[right]*(right-left));
right--;
}
}
return res;
}
}
2.6 三数之和
-
排序数组:首先对数组进行排序,这样可以方便地使用双指针法来寻找三元组。
-
遍历数组:使用一个for循环遍历数组,每次固定一个数
nums[i]
,然后在其后的数组中寻找另外两个数,使得这三个数的和为零。 -
跳过重复元素:在遍历时,如果当前数与前一个数相同,则跳过,以避免重复的三元组。
-
双指针法:对于每个固定的数
nums[i]
,使用两个指针left
和right
分别指向i+1
和数组的末尾。通过调整这两个指针来寻找另外两个数。-
如果
nums[left] + nums[right] > target
,说明和太大,需要减小和,所以移动right
指针。 -
如果
nums[left] + nums[right] < target
,说明和太小,需要增大和,所以移动left
指针。 -
如果
nums[left] + nums[right] == target
,则找到一个符合条件的三元组,将其加入结果列表中。
-
-
跳过重复元素:在找到一个三元组后,继续移动
left
和right
指针,跳过重复的元素,以避免重复的三元组。 -
返回结果:最后返回所有找到的三元组。
class Solution {
List<List<Integer>>res;
public List<List<Integer>> threeSum(int[] nums) {
res = new ArrayList<>();
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
if(nums[i]>0){
break;
}
if(i>0&&nums[i]==nums[i-1]){
continue;
}
int target = -1 * nums[i];
int left =i+1, right=nums.length-1;
while(left<right){
int sum = nums[left]+nums[right];
if(sum>target){
right--;
}else if (sum<target){
left++;
}else{
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
while(left<right&&nums[left]==nums[left+1]){
left++;
}
while(left<right&&right>0&&nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}
}
}
return res;
}
}
2.7 接雨水
每个格子能承载的雨水量为该格子左边的最大值lmax和右边的最大值rmax二者中最小的(短板效应)那一个减去本格的高度。所以我们就知道了顺序遍历和逆序遍历是要分别获取每个格子左边的最大值lmax和右边的最大值rmax
-
初始化数组:创建两个数组
left_height
和right_height
,分别用于存储每个位置左边和右边的最大高度。 -
计算左边最大高度:
-
从左到右遍历高度数组
height
。 -
对于每个位置
i
,left_height[i]
存储的是从位置0
到位置i
的最大高度。 -
具体实现是:
left_height[i] = Math.max(left_height[i-1], height[i])
。
-
-
计算右边最大高度:
-
从右到左遍历高度数组
height
。 -
对于每个位置
i
,right_height[i]
存储的是从位置i
到数组末尾的最大高度。 -
具体实现是:
right_height[i] = Math.max(right_height[i+1], height[i])
。
-
-
计算接住的雨水总量:
-
遍历每个位置
i
,计算当前位置能够接住的雨水量。 -
当前位置能够接住的雨水量为
Math.min(left_height[i], right_height[i]) - height[i]
。 -
将每个位置的雨水量累加到结果
res
中。
-
class Solution {
public int trap(int[] height) {
int len = height.length;
int []left_height=new int [len];
left_height[0]=height[0];
for(int i=1;i<len;i++){
left_height[i]=Math.max(left_height[i-1], height[i]);
}
int [] right_height = new int [len];
right_height[len-1]=height[len-1];
for(int i=len-2;i>=0;i--){
right_height[i]=Math.max(right_height[i+1], height[i]);
}
int res =0;
for(int i=0;i<len;i++){
res = res + Math.min(right_height[i], left_height[i]) - height[i];
}
return res;
}
}
滑动窗口
2.8 无重复字符的最长子串
-
滑动窗口:
-
使用两个指针来表示当前的窗口,通常称为左指针
j
和右指针i
。 -
右指针
i
用于遍历字符串中的每个字符,而左指针j
用于维护当前窗口的左边界。
-
-
哈希表:
-
使用一个哈希表
map
来记录每个字符最近一次出现的位置。 -
这样我们可以快速判断一个字符是否在当前窗口中已经出现过。
-
-
遍历字符串:
-
对于每个字符
s.charAt(i)
,检查它是否已经在哈希表中出现过。 -
如果出现过,说明当前窗口中有重复字符,需要更新窗口的左边界
j
。 -
更新
j
为该字符上次出现的位置之后的位置,以确保窗口内没有重复字符。
-
-
更新窗口和结果:
-
将当前字符及其位置
i
存入哈希表中。 -
计算当前窗口的长度为
i - j
,并更新结果res
为当前窗口长度与之前结果的最大值。
-
-
返回结果:
- 最后返回最长无重复字符子串的长度
res
。
- 最后返回最长无重复字符子串的长度
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer>map=new HashMap<>();
int j=-1;
int res = 0;
for(int i=0;i<s.length();i++){
if (map.containsKey(s.charAt(i))){
j=Math.max(j, map.get(s.charAt(i)));
}
map.put(s.charAt(i),i);
res = Math.max(i-j,res);
}
return res;
}
}
2.9 找到字符串当中的所有异位词
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
-
初始化频率数组:
-
创建一个长度为26的数组
p_frequence
来存储字符串p
中每个字母的频率。 -
遍历字符串
p
,更新p_frequence
中对应字母的频率。
-
-
滑动窗口:
-
使用两个指针
left
和right
来表示当前窗口的左右边界,初始时窗口大小为p
的长度。 -
left
初始化为0,right
初始化为pLen - 1
。
-
-
遍历字符串
s
:-
在
s
中,从left
到right
位置,计算当前窗口内每个字母的频率,存储在数组s_frequence
中。 -
使用
Arrays.equals(p_frequence, s_frequence)
来判断当前窗口内的子串是否是p
的字母异位词。 -
如果是,则将
left
加入结果列表res
中。
-
-
移动窗口:
- 每次将窗口向右移动一个位置,即
left++
和right++
,然后继续检查新的窗口。
- 每次将窗口向右移动一个位置,即
-
返回结果:
- 最后返回所有找到的字母异位词的起始索引列表
res
。
- 最后返回所有找到的字母异位词的起始索引列表
class Solution {
List<Integer>res=new ArrayList<>();
public List<Integer> findAnagrams(String s, String p) {
int sLen =s.length(),pLen=p.length();
int [] p_frequence = new int [26];
for(char c:p.toCharArray()){
p_frequence[c-'a']++;
}
int left =0, right =pLen-1;
while(right<sLen){
int []s_frequence=new int [26];
for(int i=left;i<=right;i++){
s_frequence[s.charAt(i)-'a']++;
}
if(Arrays.equals(p_frequence, s_frequence)){
res.add(left);
}
left++;
right++;
}
return res;
}
}
2.10 和为K的子数组
-
初始化一个哈希表
map
,并将键值对(0, 1)
放入其中。这是因为如果一个子数组本身的和等于 k,那么从开始到该位置的前缀和减去 k 应该等于 0。 -
初始化两个变量:
res
用于记录满足条件的子数组的个数,sum
用于记录当前的前缀和。 -
遍历数组
nums
:-
对于每个元素,更新当前的前缀和
sum
。 -
检查哈希表中是否存在键
sum - k
。如果存在,说明从某个位置到当前位置的子数组和为 k,因此将map.get(sum - k)
的值加到res
中。 -
更新哈希表,将当前的前缀和
sum
的出现次数加一。
-
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer>map=new HashMap<>();
map.put(0,1);
int res=0;
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
if(map.containsKey(sum-k)){
res+=map.get(sum-k);
}
map.put(sum, map.getOrDefault(sum,0)+1);
}
return res;
}
}
2.11 滑动窗口最大值
-
初始化:
-
创建一个双端队列
queue
,用于存储数组元素的索引。 -
创建一个列表
res
,用于存储每个窗口的最大值。
-
-
遍历数组:
- 对于数组中的每个元素
nums[i]
,执行以下操作:
- 对于数组中的每个元素
-
维护队列的有效性:
- 如果队列不为空,并且队列的最后一个元素的索引已经不在当前窗口内(即
i-k+1 > queue.peekLast()
),则从队列的末尾移除该索引。
- 如果队列不为空,并且队列的最后一个元素的索引已经不在当前窗口内(即
-
维护队列的单调性:
- 在队列中保持一个单调递减的顺序(从队首到队尾)。如果当前元素
nums[i]
大于队列中存储的元素(通过索引访问),则从队首开始移除这些元素的索引,直到队列为空或当前元素不再大于队列中的元素。
- 在队列中保持一个单调递减的顺序(从队首到队尾)。如果当前元素
-
添加当前元素索引:
- 将当前元素的索引
i
添加到队列的头部。
- 将当前元素的索引
-
记录最大值:
- 当窗口的起始位置
i-k+1
大于等于0时,说明窗口已经形成,此时队列的最后一个元素就是当前窗口的最大值,将其对应的数组值添加到结果列表res
中。
- 当窗口的起始位置
-
返回结果:
- 将结果列表
res
转换为数组并返回。
- 将结果列表
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 创建一个双端队列,用于存储数组元素的索引
LinkedList<Integer> queue = new LinkedList<>();
// 创建一个列表用于存储每个窗口的最大值
List<Integer> res = new ArrayList<>();
// 遍历数组中的每个元素
for (int i = 0; i < nums.length; i++) {
// 如果队列不为空且队列的最后一个元素的索引已经不在当前窗口内,则从队列的末尾移除该索引
if (!queue.isEmpty() && i - k + 1 > queue.peekLast()) {
queue.pollLast();
}
// 在队列中保持一个单调递减的顺序
// 如果当前元素大于队列中存储的元素,则从队首开始移除这些元素的索引
while (!queue.isEmpty() && nums[i] > nums[queue.peekFirst()]) {
queue.pollFirst();
}
// 将当前元素的索引添加到队列的头部
queue.offerFirst(i);
// 当窗口的起始位置大于等于0时,说明窗口已经形成
// 此时队列的最后一个元素就是当前窗口的最大值,将其对应的数组值添加到结果列表中
if (i - k + 1 >= 0) {
res.add(nums[queue.peekLast()]);
}
}
// 将结果列表转换为数组并返回
return res.stream().mapToInt(i -> i).toArray();
}
}
2.12 最小覆盖子串
-
初始化:
-
创建一个数组
t_index
,用于存储字符串t
中每个字符的出现次数。 -
遍历字符串
t
,更新t_index
数组。
-
-
滑动窗口:
-
初始化左右指针
left
和right
,用于表示当前窗口的左右边界。 -
初始化
t_len
为字符串t
的长度,表示还需要匹配的字符数量。 -
初始化
res
为空字符串,表示当前找到的最小覆盖子串。 -
初始化
res_len
为最大整数值,表示当前找到的最小覆盖子串的长度。 -
初始化
s_len
为字符串s
的长度。
-
-
遍历字符串
s
:-
当
right
小于s_len
时,执行以下操作:-
如果当前字符在
t_index
中的计数大于0,则减少t_len
。 -
增加
right
指针,扩大窗口。
-
-
-
收缩窗口:
-
当
t_len
为0时,说明当前窗口包含了字符串t
的所有字符:-
如果当前窗口的长度小于
res_len
,更新res
和res_len
。 -
如果当前字符在
t_index
中的计数大于等于0,则增加t_len
。 -
增加
left
指针,收缩窗口。
-
-
class Solution {
public String minWindow(String s, String t) {
// 创建一个数组用于存储字符串 t 中每个字符的出现次数
int[] t_index = new int[128];
// 遍历字符串 t,更新 t_index 数组
for (char c : t.toCharArray()) {
t_index[c]++;
}
// 初始化左右指针,用于表示当前窗口的左右边界
int left = 0, right = 0;
// 初始化 t_len 为字符串 t 的长度,表示还需要匹配的字符数量
int t_len = t.length();
// 初始化 res 为空字符串,表示当前找到的最小覆盖子串
String res = "";
// 初始化 res_len 为最大整数值,表示当前找到的最小覆盖子串的长度
int res_len = Integer.MAX_VALUE;
// 初始化 s_len 为字符串 s 的长度
int s_len = s.length();
// 当 right 小于 s_len 时,执行以下操作
while (right < s_len) {
// 如果当前字符在 t_index 中的计数大于0,则减少 t_len
if (t_index[s.charAt(right++)]-- > 0) t_len--;
// 当 t_len 为0时,说明当前窗口包含了字符串 t 的所有字符
while (t_len == 0) {
// 如果当前窗口的长度小于 res_len,更新 res 和 res_len
if (right - left < res_len) {
res = s.substring(left, right);
res_len = right - left;
}
// 如果当前字符在 t_index 中的计数大于等于0,则增加 t_len
if (t_index[s.charAt(left++)]++ >= 0) t_len++;
}
}
// 返回找到的最小覆盖子串
return res;
}
}
2.13 最大子数组和
思路:
走完这一生
如果我和你在一起会变得更好,那我们就在一起,否则我就丢下你。
我回顾我最光辉的时刻就是和不同人在一起,变得更好的最长连续时刻
class Solution {
public int maxSubArray(int[] nums) {
int res=nums[0];
int len =nums.length;
int []dp=new int [len];
dp[0]=nums[0];
for(int i=1;i<len;i++){
dp[i]=Math.max(dp[i-1]+nums[i], nums[i]);
res=Math.max(res, dp[i]);
}
return res;
}
}
2.14 合并区间
-
排序区间:首先根据每个区间的起始位置对所有区间进行排序。
-
合并区间:遍历排序后的区间列表,检查当前区间和下一个区间是否重叠。如果重叠,则合并这两个区间。
-
保存结果:将合并后的区间保存到结果列表中。
-
返回结果:将结果列表转换为二维数组并返回。
class Solution {
public int[][] merge(int[][] intervals) {
// 按照每个区间的起始位置对区间进行排序
Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
// 创建一个列表用于存储合并后的区间
List<int[]> res = new ArrayList<>();
// 遍历所有区间,进行合并操作
for (int i = 0; i < intervals.length - 1; i++) {
// 如果当前区间的结束位置大于等于下一个区间的起始位置,说明有重叠
if (intervals[i][1] >= intervals[i + 1][0]) {
// 合并区间:更新下一个区间的起始和结束位置
intervals[i + 1][0] = Math.min(intervals[i + 1][0], intervals[i][0]);
intervals[i + 1][1] = Math.max(intervals[i + 1][1], intervals[i][1]);
} else {
// 如果没有重叠,将当前区间加入结果列表
res.add(intervals[i]);
}
}
// 将最后一个区间加入结果列表
res.add(intervals[intervals.length - 1]);
// 将结果列表转换为二维数组
int[][] arr = new int[res.size()][2];
for (int i = 0; i < res.size(); i++) {
arr[i] = res.get(i);
}
// 返回合并后的区间数组
return arr;
}
}
2.15 轮转数组
-
双倍数组:将原数组复制两次形成一个新的数组,这样可以方便地处理旋转操作。
-
计算有效旋转步数:由于旋转
len
次相当于没有旋转,因此只需要旋转k % len
次。 -
复制旋转后的结果:从双倍数组中截取旋转后的结果并复制回原数组。
class Solution {
public void rotate(int[] nums, int k) {
// 获取数组的长度
int len = nums.length;
// 创建一个长度为原数组两倍的新数组
int[] arr = new int[len * 2];
// 计算有效的旋转步数,避免多余的完整旋转
k = k % len;
// 将原数组复制到新数组的前半部分
System.arraycopy(nums, 0, arr, 0, len);
// 将原数组再次复制到新数组的后半部分
System.arraycopy(nums, 0, arr, len, len);
// 从新数组中截取旋转后的结果,复制回原数组
System.arraycopy(arr, len - k, nums, 0, len);
}
}
2.16 除自身以外数组的乘积
238. 除自身以外数组的乘积 - 力扣(LeetCode)
-
左积数组:创建一个数组
left
,其中left[i]
存储的是nums[0]
到nums[i-1]
的乘积。 -
右积数组:创建一个数组
right
,其中right[i]
存储的是nums[i+1]
到nums[len-1]
的乘积。 -
计算结果:对于每个位置
i
,结果res[i]
是left[i]
和right[i]
的乘积
class Solution {
public int[] productExceptSelf(int[] nums) {
// 获取数组的长度
int len = nums.length;
// 创建左积数组,并初始化第一个元素为1
int[] left = new int[len];
left[0] = 1;
// 计算左积数组,left[i] 为 nums[0] 到 nums[i-1] 的乘积
for (int i = 1; i < len; i++) {
left[i] = nums[i - 1] * left[i - 1];
}
// 创建右积数组,并初始化最后一个元素为1
int[] right = new int[len];
right[len - 1] = 1;
// 计算右积数组,right[i] 为 nums[i+1] 到 nums[len-1] 的乘积
for (int i = len - 2; i >= 0; i--) {
right[i] = right[i + 1] * nums[i + 1];
}
// 创建结果数组
int[] res = new int[len];
// 计算结果数组,res[i] 为 left[i] 和 right[i] 的乘积
for (int i = 0; i < len; i++) {
res[i] = left[i] * right[i];
}
// 返回结果数组
return res;
}
}
2.17 缺失的第一个正数
-
原地哈希:通过交换元素的位置,将每个正整数
n
放到索引n-1
的位置上。 -
查找缺失的正整数:遍历数组,找到第一个位置
i
,使得nums[i]
不等于i+1
,那么i+1
就是缺失的正整数。 -
返回结果:如果所有位置都满足
nums[i] == i+1
,则返回len + 1
。
class Solution {
public int firstMissingPositive(int[] nums) {
// 获取数组的长度
int len = nums.length;
// 将每个正整数 n 放到索引 n-1 的位置上
for (int i = 0; i < len; i++) {
// 交换条件:nums[i] 在范围内,且不在正确的位置上
while (nums[i] <= len && nums[i] > 0 && nums[i] != nums[nums[i] - 1]) {
// 交换 nums[i] 和 nums[nums[i] - 1]
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
// 查找第一个缺失的正整数
for (int i = 0; i < len; i++) {
// 如果 nums[i] 不等于 i+1,说明 i+1 是缺失的正整数
if (nums[i] != i + 1) {
return i + 1;
}
}
// 如果所有位置都正确,返回 len + 1
return len + 1;
}
}
矩阵
2.18 矩阵置0
-
记录零的位置:使用两个集合
rowSet
和colSet
分别记录需要置零的行和列。 -
遍历矩阵:第一次遍历矩阵,找到所有为 0 的元素,并记录其所在的行和列。
-
置零操作:根据记录的行和列,第二次遍历矩阵,将对应的行和列置为 0。
class Solution {
public void setZeroes(int[][] matrix) {
// 创建集合用于记录需要置零的行和列
Set<Integer> rowSet = new HashSet<>();
Set<Integer> colSet = new HashSet<>();
// 遍历矩阵,记录所有为 0 的元素的行和列
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[0].length; col++) {
if (matrix[row][col] == 0) {
// 将行和列添加到对应的集合中
rowSet.add(row);
colSet.add(col);
}
}
}
// 将记录的行全部置为 0
for (int row : rowSet) {
Arrays.fill(matrix[row], 0);
}
// 将记录的列全部置为 0
for (int col : colSet) {
for (int row = 0; row < matrix.length; row++) {
matrix[row][col] = 0;
}
}
}
}
2.19 螺旋矩阵
-
初始化边界:定义矩阵的上下左右边界。
-
螺旋遍历:按照螺旋顺序依次遍历矩阵的元素,并将其添加到结果列表中。
-
边界更新:每次遍历完一条边后,更新边界,直到所有元素都被遍历。
class Solution {
// 定义结果列表
List<Integer> res;
public List<Integer> spiralOrder(int[][] matrix) {
// 初始化结果列表
res = new ArrayList<>();
// 获取矩阵的行数和列数
int m = matrix.length, n = matrix[0].length;
// 定义上下左右边界
int left = 0, right = matrix[0].length - 1, t = 0, b = matrix.length - 1;
// 开始螺旋遍历
while (true) {
// 从左到右遍历当前上边界
for (int i = left; i <= right; i++) {
res.add(matrix[t][i]);
}
// 检查是否遍历完所有元素
if (res.size() == m * n) {
break;
}
// 更新上边界
t++;
// 从上到下遍历当前右边界
for (int i = t; i <= b; i++) {
res.add(matrix[i][right]);
}
// 检查是否遍历完所有元素
if (res.size() == m * n) {
break;
}
// 更新右边界
right--;
// 从右到左遍历当前下边界
for (int i = right; i >= left; i--) {
res.add(matrix[b][i]);
}
// 检查是否遍历完所有元素
if (res.size() == m * n) {
break;
}
// 更新下边界
b--;
// 从下到上遍历当前左边界
for (int i = b; i >= t; i--) {
res.add(matrix[i][left]);
}
// 检查是否遍历完所有元素
if (res.size() == m * n) {
break;
}
// 更新左边界
left++;
}
// 返回结果列表
return res;
}
}
2.20 旋转图像
class Solution {
public void rotate(int[][] matrix) {
int n=matrix.length;
int [][]t = new int [n][n];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
t[i][j]=matrix[i][j];
}
}
for(int i=0;i<t.length;i++){
for(int j=0;j<n;j++){
matrix[j][n-i-1]=t[i][j];
}
}
}
}
2.21 搜索二维矩阵 II
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int row=0, col=n-1;
while(row<m&&col>=0){
if(matrix[row][col]==target){
return true;
}else if (matrix[row][col]<target){
row++;
}else{
col--;
}
}
return false;
}
}
链表
2.22 相交链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA;
ListNode B = headB;
while(A!=B){
A=A==null? headB:A.next;
B=B==null? headA:B.next;
}
return A;
}
}
2.23 反转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode dummpy = new ListNode(0,head);
ListNode g=dummpy, p=g.next;
while(p!=null&&p.next!=null){
ListNode temp = p.next;
p.next=temp.next;
temp.next=g.next;
g.next=temp;
}
return dummpy.next;
}
}
2.24 回文链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer>list=new ArrayList<>();
ListNode node =head;
while(node!=null){
list.add(node.val);
node=node.next;
}
int left=0, right=list.size()-1;
while(left<right){
if(list.get(left++)!=list.get(right--)){
return false;
}
}
return true;
}
}
2.25 环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head,fast=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
}
2.26 环形链表 II
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow=head,fast=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
return null;
}
}
2.27 合并两个有序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummpy = new ListNode(0);
ListNode p = dummpy;
while(list1!=null&&list2!=null){
if(list1.val<list2.val){
p.next=list1;
list1=list1.next;
p=p.next;
}else{
p.next=list2;
list2=list2.next;
p=p.next;
}
}
p.next=list1==null? list2:list1;
return dummpy.next;
}
}
2.28 两数相加
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode newList=new ListNode();
ListNode newL=newList;
int jin=0;
while(l1!=null||l2!=null){
int sum=(l1!=null? l1.val:0)+(l2!=null? l2.val:0)+jin;
jin=sum/10;
int count=sum%10;
ListNode newNode=new ListNode(count);
newL.next=newNode;
newL=newL.next;
l1= l1!=null? l1.next:null;
l2= l2!=null? l2.next:null;
}
if(jin!=0){
newL.next=new ListNode(jin);
}
return newList.next;
}
}
2.29 删除链表的倒数第 N 个结点
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummpy = new ListNode(0, head);
ListNode p = dummpy;
int len =0;
while(p!=null){
p=p.next;
len++;
}
ListNode point =dummpy;
int index = len-n-1;
while(index>0){
point=point.next;
index--;
}
point.next=point.next.next;
return dummpy.next;
}
}
优化思路:
-
双指针一次遍历:通过快指针先走
n
步,再同步移动双指针,实现单次遍历完成定位。 -
虚拟头节点:统一处理头节点删除场景(如删除第一个节点)。
-
边界处理:
-
fast
移动n
步时检测空指针,防止n
超过链表长度。 -
fast
最终停在最后一个节点(fast.next == null
),此时slow
指向倒数第n+1
个节点。
-
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head); // 创建虚拟头节点
ListNode fast = dummy, slow = dummy; // 双指针初始化
// 快指针先移动n步(修正为n步而非n+1步)
for (int i = 0; i < n; i++) {
if (fast == null) return head; // 处理n超过链表长度的情况
fast = fast.next;
}
// 同步移动双指针直到快指针到达末尾
while (fast.next != null) { // 关键点:fast停在最后一个节点
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next; // 删除倒数第n个节点
return dummy.next; // 返回修正后的头节点
}
}
2.30 两两交换链表中的节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummpy = new ListNode(0, head);
ListNode g=dummpy, p=dummpy.next;
while(p!=null&&p.next!=null){
ListNode temp = p.next;
p.next=temp.next;
temp.next=g.next;
g.next=temp;
g=p;
p=p.next;
}
return dummpy.next;
}
}
2.31 K 个一组翻转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if(k==1){
return head;
}
ListNode dummpy = new ListNode(0, head);
ListNode point =dummpy.next;
int len =0;
while(point!=null){
point=point.next;
len++;
}
int count = len/k;
int index =0;
ListNode g = dummpy, p=dummpy.next;
while(count>0){
ListNode temp = p.next;
p.next=temp.next;
temp.next=g.next;
g.next=temp;
index++;
if(index+1==k){
index=0;
g=p;
p=p.next;
count--;
}
}
return dummpy.next;
}
}
2.32 随机链表的复制
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
Map<Node, Node>map=new HashMap<>();
public Node copyRandomList(Node head) {
Node point =head;
while(point!=null){
map.put(point, new Node(point.val));
point = point.next;
}
Set<Node>key=map.keySet();
for(Node node: key){
map.get(node).next=map.get(node.next);
map.get(node).random=map.get(node.random);
}
return map.get(head);
}
}
2.33 排序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode slow =head, fast = head.next;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode temp=slow.next;
slow.next=null;
slow=head;
ListNode l1=sortList(temp);
ListNode l2 = sortList(slow);
ListNode dummpy = new ListNode(0);
ListNode p =dummpy;
while(l1!=null&&l2!=null){
if(l1.val>l2.val){
p.next=l2;
l2=l2.next;
p=p.next;
}else{
p.next=l1;
l1=l1.next;
p=p.next;
}
}
p.next=l1==null? l2:l1;
return dummpy.next;
}
}
2.34 合并 K 个升序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
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)/2;
ListNode l1=merge(lists, left, mid);
ListNode l2=merge(lists, mid+1, right);
return mergeTwoList(l1,l2);
}
private ListNode mergeTwoList(ListNode l1, ListNode l2){
ListNode dummpy = new ListNode(0);
ListNode p =dummpy;
while(l1!=null&&l2!=null){
if(l1.val>l2.val){
p.next=l2;
l2=l2.next;
p=p.next;
}else{
p.next=l1;
l1=l1.next;
p=p.next;
}
}
p.next =l1==null? l2:l1;
return dummpy.next;
}
}
2.35 LRU 缓存
class DlinkedNode {
DlinkedNode next;
DlinkedNode prev;
int key;
int value;
public DlinkedNode(){}
public DlinkedNode(int key, int value){
this.key=key;
this.value=value;
}
}
class LRUCache {
Map<Integer, DlinkedNode>map=new HashMap<>();
DlinkedNode head = new DlinkedNode();
DlinkedNode tail = new DlinkedNode();
int capacity;
int size =0;
public LRUCache(int capacity) {
this.capacity =capacity;
head.next=tail;
tail.prev=head;
}
public int get(int key) {
if (!map.containsKey(key)){
return -1;
}
moveToHead(map.get(key));
return map.get(key).value;
}
public void put(int key, int value) {
DlinkedNode node = new DlinkedNode(key, value);
if(!map.containsKey(key)){
map.put(key, node);
insert(node);
size++;
if(size>capacity){
DlinkedNode temp=tail.prev;
map.remove(temp.key);
remove(temp);
size--;
}
}else{
DlinkedNode temp = map.get(key);
remove(temp);
map.put(key, node);
insert(node);
}
}
private void moveToHead(DlinkedNode node){
remove(node);
insert(node);
}
private void remove(DlinkedNode node){
node.next.prev=node.prev;
node.prev.next=node.next;
}
private void insert(DlinkedNode node){
node.next=head.next;
head.next.prev=node;
node.prev=head;
head.next=node;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
二叉树
2.36 二叉树的中序遍历
递归:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer>res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null){
return res;
}
inorderTraversal(root.left);
res.add(root.val);
inorderTraversal(root.right);
return res;
}
}
非递归:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer>res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null){
return res;
}
LinkedList<TreeNode>stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur = stack.pop();
if(cur!=null){
if(cur.right!=null){
stack.push(cur.right);
}
stack.push(cur);
stack.push(null);
if(cur.left!=null){
stack.push(cur.left);
}
}else{
cur=stack.pop();
res.add(cur.val);
}
}
return res;
}
}
2.37 二叉树的最大深度
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root==null){
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right))+1;
}
}
2.38 翻转二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root==null){
return null;
}
TreeNode left = invertTree(root.right);
TreeNode right = invertTree(root.left);
root.left=left;
root.right=right;
return root;
}
}
2.39 对称二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
if(root.left==null&&root.right==null){
return true;
}
if(root.left==null||root.right==null){
return false;
}
return isOk(root.left, root.right);
}
private boolean isOk(TreeNode left, TreeNode right){
if(left==null&&right==null){
return true;
}
if(left==null||right==null){
return false;
}
return isOk(left.left, right.right)&&isOk(left.right, right.left)&&left.val==right.val;
}
}
2.40 二叉树的直径
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res = Integer.MIN_VALUE;
public int diameterOfBinaryTree(TreeNode root) {
maxPath(root);
return res;
}
private int maxPath(TreeNode root){
if(root==null){
return 0;
}
int l = maxPath(root.left);
int r = maxPath(root.right);
res = Math.max(res, l+r);
return Math.max(r, l)+1;
}
}
2.41 二叉树的层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<List<Integer>>res;
List<Integer>path;
public List<List<Integer>> levelOrder(TreeNode root) {
res = new ArrayList<>();
if(root==null){
return res;
}
LinkedList<TreeNode>queue = new LinkedList<>();
queue.push(root);
while(!queue.isEmpty()){
path = new ArrayList<>();
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode cur= queue.poll();
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
path.add(cur.val);
}
res.add(new ArrayList(path));
}
return res;
}
}
2.42 将有序数组转换为二叉搜索树
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return buildBstTree(nums, 0, nums.length);
}
private TreeNode buildBstTree(int[]nums, int left, int right){
if(left>=right){
return null;
}
int mid = (left+right)/2;
TreeNode root = new TreeNode(nums[mid]);
root.left=buildBstTree(nums, left, mid);
root.right=buildBstTree(nums, mid+1, right);
return root;
}
}
2.43 验证二叉搜索树
主要思路:由于二叉搜索树数据是左小根中右大,所以用中序遍历出来的数据应该是递增的,所以这里直接中序遍历判断是不是递增的即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
long preNum = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root==null){
return true;
}
boolean l = isValidBST(root.left);
if (root.val<=preNum){
return false;
}
preNum=root.val;
boolean r = isValidBST(root.right);
return l && r;
}
}
2.44 二叉搜索树中第K小的元素
230. 二叉搜索树中第 K 小的元素 - 力扣(LeetCode)
主要思路:这里主要用到的是非递归的中序遍历模板,因为二叉搜索树按照中序遍历是有序的,所以每一次遍历到某个节点的时候k--,当k=0的时候也就是第k小的元素。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int kthSmallest(TreeNode root, int k) {
if(root == null){
return -1;
}
LinkedList<TreeNode>stack = new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur = stack.pop();
if(cur!=null){
if(cur.right!=null){
stack.push(cur.right);
}
stack.push(cur);
stack.push(null);
if(cur.left!=null){
stack.push(cur.left);
}
}else{
cur =stack.pop();
k--;
if(k==0){
return cur.val;
}
}
}
return -1;
}
}
2.45 二叉树的右视图
主要思路:二叉树的层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer>res = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
if (root==null){
return res;
}
LinkedList<TreeNode>queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode cur = queue.poll();
if (i==size-1){
res.add(cur.val);
}
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
return res;
}
}
2.46 二叉树展开为链表
主要思路:二叉树的非递归方式的前序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
if(root==null){
return;
}
LinkedList<TreeNode>stack = new LinkedList<>();
TreeNode newRoot = new TreeNode();
TreeNode p = newRoot;
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur = stack.pop();
if(cur!=null){
if(cur.right!=null){
stack.push(cur.right);
}
if(cur.left!=null){
stack.push(cur.left);
}
stack.push(cur);
stack.push(null);
}else{
cur=stack.pop();
p.right = cur;
p.left=null;
p=p.right;
}
}
root=newRoot.right;
}
}
2.47 从前序与中序遍历序列构造二叉树
105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Map<Integer,Integer>map=new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return buildBinaryTree(preorder,0, inorder.length-1, inorder,0, inorder.length-1);
}
private TreeNode buildBinaryTree(int[]preorder, int preStartIndex, int preEndIndex, int[]inorder, int inStartIndex, int inEndIndex){
if(preStartIndex>preEndIndex||inStartIndex>inEndIndex){
return null;
}
int index = map.get(preorder[preStartIndex]);
int diff = index-inStartIndex;
TreeNode root=new TreeNode(preorder[preStartIndex]);
root.left=buildBinaryTree(preorder, preStartIndex+1, preStartIndex+diff, inorder, inStartIndex, index-1);
root.right=buildBinaryTree(preorder, preStartIndex+diff+1, preEndIndex, inorder, index+1, inEndIndex);
return root;
}
}
2.48 路径总和3
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Map<Long, Integer>map=new HashMap<>();
int res =0;
public int pathSum(TreeNode root, int targetSum) {
map.put(0L, 1);
findPathSum(root, targetSum, 0L);
return res;
}
private void findPathSum(TreeNode root, int targetSum, Long currentSum){
if(root==null){
return;
}
currentSum+=root.val;
res +=map.getOrDefault(currentSum-targetSum, 0);
map.put(currentSum, map.getOrDefault(currentSum,0)+1);
findPathSum(root.left, targetSum, currentSum);
findPathSum(root.right, targetSum, currentSum);
map.put(currentSum, map.get(currentSum)-1);
currentSum-=root.val;
}
}
2.49 二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||p==root||q==root){
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;
}
return root;
}
}
2.50 二叉树中的最大路径和
124. 二叉树中的最大路径和 - 力扣(LeetCode)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
findMaxPathSum(root);
return res;
}
private int findMaxPathSum(TreeNode root){
if(root==null){
return 0;
}
int l = findMaxPathSum(root.left);
int r = findMaxPathSum(root.right);
res = Math.max(l+r+root.val, res);
return Math.max(0, Math.max(r,l)+root.val);
}
}
图论
图搜的模板:(bfs和dfs掌握一个就行,我一般掌握bfs)
// 定义四个方向的数组
private static final int[][] DIRS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
public static void bfs(char[][] grid, boolean[][] visited, int x, int y) {
Queue<int[]> queue = new LinkedList<>(); // 定义队列
queue.offer(new int[]{x, y}); // 起始节点加入队列
visited[x][y] = true; // 只要加入队列,立刻标记为访问过的节点
while (!queue.isEmpty()) { // 开始遍历队列里的元素
int[] cur = queue.poll(); // 从队列取元素
int curx = cur[0];
int cury = cur[1]; // 当前节点坐标
for (int i = 0; i < DIRS.length; i++) { // 开始向当前节点的四个方向左右上下去遍历
int nextx = curx + DIRS[i][0];
int nexty = cury + DIRS[i][1]; // 获取周边四个方向的坐标
// 坐标越界了,直接跳过
if (nextx < 0 || nextx >= grid.length || nexty < 0 || nexty >= grid[0].length) continue;
if (!visited[nextx][nexty]) { // 如果节点没被访问过
queue.offer(new int[]{nextx, nexty}); // 队列添加该节点为下一轮要遍历的节点
visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
}
}
}
}
2.51 岛屿数量
解题思路:图搜模板
class Solution {
int [][]dir = {{0,1}, {1,0}, {-1,0},{0,-1}};
int res =0;
boolean [][]visited;
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
visited= new boolean[m][n];
for(int i=0;i<m;i++){
for(int j =0; j<n;j++){
if(grid[i][j]=='1'&&!visited[i][j]){
bfs(grid, i, j);
res++;
}
}
}
return res;
}
private void bfs(char[][]grid, int x, int y){
int m = grid.length, n = grid[0].length;
LinkedList<int[]>queue=new LinkedList<>();
visited[x][y]=true;
queue.offer(new int []{x,y});
while(!queue.isEmpty()){
int[] cur = queue.poll();
for(int i=0;i<4;i++){
int newX = cur[0]+dir[i][0];
int newY = cur[1]+dir[i][1];
if(newX<0||newY<0||newX>=m||newY>=n){
continue;
}
if(grid[newX][newY]=='1'&&!visited[newX][newY]){
visited[newX][newY]=true;
queue.offer(new int []{newX,newY});
}
}
}
}
}
2.52 腐烂的橘子
主要思路:这里主要架构都还是图搜的模板结构,只不过进行了一些变形,但是总体是不变的。首先把原腐烂的橘子给收集起来,收集之后再利用bfs进行图搜,搜到不腐烂的橘子的时候将其变为腐烂,并且收集当前即将变为腐烂的橘子。最后遍历查看还有没有没有腐烂的橘子。在bfs的时候我代码里有个flag标志,主要是因为在最后一次遍历的时候可能都是腐烂的了,所以res不需要++也不需要queue1=queue2;
class Solution {
LinkedList<int[]>queue1=new LinkedList<>();
int res = 0;
int [][]dir={{-1,0},{1,0}, {0,1},{0,-1}};
public int orangesRotting(int[][] grid) {
int m = grid.length, n = grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if (grid[i][j]==2){
queue1.offer(new int []{i,j});
}
}
}
bfs(grid, m, n);
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1){
return -1;
}
}
}
return res;
}
private void bfs(int[][]grid, int m, int n){
LinkedList<int[]>queue2 = new LinkedList<>();
boolean flag =false;
while(!queue1.isEmpty()){
int [] node = queue1.poll();
for(int i=0;i<4;i++){
int nodeX = node[0] + dir[i][0];
int nodeY = node[1] + dir[i][1];
if(nodeX<0||nodeY<0||nodeX>=m||nodeY>=n){
continue;
}
if(grid[nodeX][nodeY]==1){
grid[nodeX][nodeY]=2;
queue2.offer(new int []{nodeX, nodeY});
flag = true;
}
}
if (queue1.isEmpty()&&flag){
res++;
flag=false;
queue1=queue2;
queue2=new LinkedList<>();
}
}
}
}
2.53 课程表
主要思路:主要借助一个入度数组和一个邻接矩阵,每一次都会将入度为0的加入到队列当中,然后遍历队列当中入度为0的节点。入度为0就说明这个课程是可以学习的,所以将总的课程数-1,然后会将对应的邻接矩阵当中的列表取出,然后再次遍历,将里面的节点的入度都-1,同时将度数为0的节点压入队列当中。如此往复。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>>adjency = new ArrayList<>();
int []indegress = new int [numCourses];
for(int i=0;i<numCourses;i++){
adjency.add(new ArrayList<>());
}
for(int []pre : prerequisites){
indegress[pre[0]]++;
adjency.get(pre[1]).add(pre[0]);
}
LinkedList<Integer>queue = new LinkedList<>();
for(int i=0;i<numCourses;i++){
if (indegress[i] ==0){
queue.offer(i);
}
}
while(!queue.isEmpty()){
int course = queue.poll();
numCourses--;
List<Integer>list = adjency.get(course);
for(int num : list){
indegress[num]--;
if (indegress[num] == 0){
queue.offer(num);
}
}
}
return numCourses == 0;
}
}
2.54 实现Trie前缀树
208. 实现 Trie (前缀树) - 力扣(LeetCode)
思路:看下这个文章基本上前缀树的题目就都会了。经典数据结构——前缀树-CSDN博客
class TireNode{
TireNode [] children = new TireNode[26];
boolean isWord;
}
class Trie {
TireNode root = new TireNode();
public Trie() {
}
public void insert(String word) {
TireNode cur = root;
for(int i=0; i<word.length();i++){
if(cur.children[word.charAt(i)-'a']==null){
cur.children[word.charAt(i)-'a']=new TireNode();
}
cur=cur.children[word.charAt(i)-'a'];
}
cur.isWord=true;
}
public boolean search(String word) {
TireNode cur = root;
for(int i=0; i<word.length();i++){
if(cur.children[word.charAt(i)-'a']==null){
return false;
}
cur=cur.children[word.charAt(i)-'a'];
}
return cur.isWord;
}
public boolean startsWith(String prefix) {
TireNode cur = root;
for(int i=0; i<prefix.length();i++){
if(cur.children[prefix.charAt(i)-'a']==null){
return false;
}
cur=cur.children[prefix.charAt(i)-'a'];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
回溯
2.55 全排列
这个基本上就是回溯算法的模板,没太多需要注意的地方,这里如果不是很清楚的话,可以看下代码随想录对这部分的讲解。
模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer>path = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
backTracking(nums, 0);
return res;
}
private void backTracking(int[]nums, int startIndex){
if (path.size()==nums.length){
res.add(new ArrayList(path));
return;
}
for(int i=startIndex;i<nums.length;i++){
if(path.contains(nums[i]))continue;
path.add(nums[i]);
backTracking(nums, startIndex);
path.removeLast();
}
}
}
2.56 子集
startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
class Solution {
List<List<Integer>>res;
List<Integer>path;
public List<List<Integer>> subsets(int[] nums) {
res = new ArrayList<>();
path = new ArrayList<>();
backTracking(nums,0);
return res;
}
private void backTracking(int [] nums, int startIndex){
res.add(new ArrayList(path));
for(int i=startIndex;i<nums.length;i++){
path.add(nums[i]);
backTracking(nums, i+1);
path.removeLast();
}
}
}
2.57 电话号码的字母组合
class Solution {
List<String>res = new ArrayList<>();
String []dir= {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
if(digits.length()==0){
return res;
}
backTracking(digits, 0);
return res;
}
StringBuffer strBuff = new StringBuffer();
private void backTracking(String digits, int startIndex){
if (digits.length()==strBuff.length()){
res.add(new String(strBuff));
return;
}
for(int i = startIndex; i<digits.length();i++){
String str = dir[digits.charAt(i)-'0'-2];
for(int j=0;j<str.length();j++){
strBuff.append(str.charAt(j));
backTracking(digits, i+1);
strBuff.deleteCharAt(strBuff.length()-1);
}
}
}
}
2.58 组合总和
startIndex控制for循环的起始位置
剪枝:
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就跳过当前的循环。
当然这里还可以先对集合进行整体从大到小的排序,只要碰到下一层的sum(就是本层的 sum + candidates[i])已经大于target情况,直接全部跳出即可。
class Solution {
List<List<Integer>> res;
List<Integer>path;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new ArrayList<>();
path=new ArrayList<>();
backTracking(candidates, target, 0,0);
return res;
}
private void backTracking(int []candidates, int target, int currentSum, int startIndex){
if(target==currentSum){
res.add(new ArrayList(path));
return;
}
for(int i = startIndex; i<candidates.length;i++){
if(currentSum+candidates[i]>target){
continue;
}
path.add(candidates[i]);
backTracking(candidates, target, currentSum+candidates[i], i);
path.removeLast();
}
}
}
2.59 括号生成
-
函数
backTracking(left, right)
当中的参数 表示当前状态下剩余的左括号数left
、右括号数right
。 -
结束条件:如果
left
为0且right
为0,这意味着一个完整的组合被生成,添加到结果列表中。 -
剪枝条件:如果
left
大于right
,这意味着当前的组合中左括号数超过右括号数,该组合不可能有效,返回。
class Solution {
List<String> res;
public List<String> generateParenthesis(int n) {
res = new ArrayList<>();
backTracking(n,n);
return res;
}
StringBuffer strBuff = new StringBuffer();
private void backTracking(int left, int right){
if(left==0&&right==left){
res.add(new String(strBuff));
return;
}
if(left>right){
return;
}
if(left>0){
strBuff.append('(');
backTracking(left-1, right);
strBuff.deleteCharAt(strBuff.length()-1);
}
if(right>0){
strBuff.append(')');
backTracking(left, right-1);
strBuff.deleteCharAt(strBuff.length()-1);
}
}
}
2.60 单词搜索
通过遍历每个潜在起始位置,并从该位置进行四向递归搜索来检测是否可形成要求的单词。通过visited
控制单元重复使用问题,动态确定并构造路径字符串。
备注📝:这个题目应该也有别的比较方便的解法,但是当时我是借助了图遍历的思路来做的这个题目,如果有比较好的思路可以评论or私信我一起探讨一下这个题解。
class Solution {
int [][]dir = {{0,1},{1,0},{-1,0}, {0,-1}};
boolean flag =false;
boolean [][]visited;
StringBuffer strBuff = new StringBuffer();
public boolean exist(char[][] board, String word) {
int m = board.length, n = board[0].length;
for(int i=0;i<m;i++){
for(int j=0; j<n;j++){
if(flag){
return flag;
}
if(board[i][j]==word.charAt(0)){
visited= new boolean[m][n];
strBuff = new StringBuffer();
backTracking(board,i,j, word);
if(flag){
return flag;
}
}
}
}
return flag;
}
private void backTracking(char[][]board, int x, int y, String word){
strBuff.append(board[x][y]);
if(strBuff.toString().equals(word)){
flag=true;
return;
}
if (flag){
return;
}
visited[x][y]=true;
for(int i=0;i<4;i++){
int newX = dir[i][0] + x;
int newY = dir[i][1]+ y;
if(newX<0||newX>=board.length||newY<0||newY>=board[0].length){
continue;
}
if(!visited[newX][newY]&&board[newX][newY]==word.charAt(strBuff.length())){
backTracking(board, newX, newY, word);
visited[newX][newY]=false;
strBuff.deleteCharAt(strBuff.length()-1);
}
}
}
}
2.61 分割回文串
题解:这里和一般性的回溯算法没有什么区别,但是由于是要找到字符串当中的回文串,所以在添加到路径path的时候需要有一个判断是否是回文串的操作。如果是回文串则加入,如果不是不做任何动作。
class Solution {
List<List<String>>res=new ArrayList<>();
List<String>path = new ArrayList<>();
public List<List<String>> partition(String s) {
backTracking(s, 0);
return res;
}
private void backTracking(String s, int startIndex){
if(startIndex==s.length()){
res.add(new ArrayList(path));
return;
}
for(int i = startIndex; i<s.length();i++){
if (isParlim(s, startIndex, i)){
path.add(s.substring(startIndex, i+1));
backTracking(s, i+1);
path.removeLast();
}
}
}
private boolean isParlim(String s, int startIndex, int endIndex){
while(startIndex<=endIndex){
if (s.charAt(startIndex++)!=s.charAt(endIndex--)){
return false;
}
}
return true;
}
}
2.62 N皇后问题
这个题目和32类似,本质都是回溯算法,但是在进行回溯之前需要先判断一下当前的节点是否符合N皇后的规则,如果不符合则直接返回,符合的话再进行回溯。
class Solution {
List<List<String>>res;
List<String>path;
char[][]chess;
public List<List<String>> solveNQueens(int n) {
res = new ArrayList<>();
path = new ArrayList<>();
chess = new char[n][n];
for(int i=0;i<n;i++){
Arrays.fill(chess[i], '.');
}
backTracking(n,0);
return res;
}
private void backTracking(int n, int row){
if(row==n){
res.add(new ArrayList(Arrays2List(chess)));
return;
}
for(int col=0;col<n;col++){
if (isValid(n, row, col)){
chess[row][col]='Q';
backTracking(n, row+1);
chess[row][col]='.';
}
}
}
private List Arrays2List(char[][]chess){
List<String>list=new ArrayList<>();
for(char[]c:chess){
list.add(new String(c));
}
return list;
}
private boolean isValid(int n, int row, int col){
for(int i=row-1;i>=0;i--){
if(chess[i][col]=='Q')return false;
}
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
if(chess[i][j]=='Q')return false;
}
for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++){
if(chess[i][j]=='Q'){
return false;
}
}
return true;
}
}
二分查找
2.63 搜索插入位置
这里一定要好好体会一下,为什么这样初始化,初始化之后边界条件是怎么确定的。基本上可以秒所有的二分查找
这里没什么需要讲的,但是需要注意一下我在这初始化right=len,left=0,也就是说左闭右开,所以right的指针是没有什么含义的,所以while里的条件是left<right,而为什么right=mid呢,因为在if(nums[mid]>target)条件已经可以判断当前的mid是不等于target的,也就是现在的mid也是没有什么含义的所以此时right=mid, 而同理因为left边界是闭着的,所以left是有含义的,所以left=mid+1;
class Solution {
public int searchInsert(int[] nums, int target) {
int len = nums.length;
int left =0, right = len;
while(left<right){
int mid = (left+right)/2;
if(nums[mid]==target){
return mid;
}
if(nums[mid]>target){
right=mid;
}else{
left=mid+1;
}
}
return left;
}
}
2.64 搜索二维矩阵
主要思路: 从右上角开始,是一棵二叉排序树,按照二叉排序树的遍历来寻找。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int row=0,col=n-1;
while(row<m&&col>=0){
if (matrix[row][col]>target){
col--;
}else if (matrix[row][col]<target){
row++;
}else{
return true;
}
}
return false;
}
}
2.65 在排序数组中查询元素的第一个和最后一个位置
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
主要思路:
这里主要是二分查找的解题模板的简单变形。
class Solution {
public int[] searchRange(int[] nums, int target) {
int len = nums.length;
int left =0, right =nums.length;
int [] res= new int [2];
Arrays.fill(res, -1);
while(left<right){
int mid = (left+right)/2;
if(nums[mid]>target){
right = mid;
}else if (nums[mid]<target){
left = mid+1;
}else{
left=right=mid;
while(left>0&&nums[left]==nums[left-1]){
left--;
}
while(right<len-1&&nums[right]==nums[right+1]){
right++;
}
res [0]=left;
res[1]=right;
return res;
}
}
return res;
}
}
2.66 搜索旋转排序数组
解题思路:先根据 nums[mid] 与 nums[left] 的关系判断 mid 是在左段还是右段,接下来再判断 target 是在 mid 的左边还是右边,从而来调整左右边界 left 和 right(这里的中心思想其实就是不管有序数组旋转多少次,它都是局部有序的状态)
class Solution {
public int search(int[] nums, int target) {
int left =0, right = nums.length, len = nums.length;
while(left<right){
int mid = (left+right)/2;
if(nums[mid]==target){
return mid;
}
if (nums[left]<=nums[mid]){//这一步的作用就是找到有序的区间
if(nums[left]<=target&&nums[mid]>=target){
right=mid;
}else{
//想一想现在落在后面的单调部分的区间了,[4,5,6,7,0,1,2] 前半部分的单调区间是4,5,6,7后半部分的单调区间是0,1,2
//我们想让区间变为后半部分,必然是left = mid+1啊,不然区间就变成了[7,0,1,2]
left=mid+1;
}
}else{
//后半部分绝对单调
if(nums[right-1]>=target&&nums[mid]<=target){
//落在后半部分的单调区间
left=mid+1;
}else{
right=mid;
}
}
}
return -1;
}
}
2.67 寻找旋转数组当中的最小值
153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
-
初始化条件:
- 数组
nums
是一个经过旋转的排序数组,但仍保持内部的部分递增特性。
- 数组
-
二分查找:
-
通过维护两个指针
left
和right
,在nums
的范围内寻找最小元素。 -
通过计算
mid
,对nums[mid]
和nums[right]
的大小关系进行判断。-
若
nums[mid] < nums[right]
,说明最小值在mid
左侧(包括mid
)。 -
若
nums[mid] >= nums[right]
,说明最小值在mid
右侧。
-
-
-
迭代收缩:
-
不断调整
left
和right
的指针直到收缩到最小值位置。 -
最终,
left
就定位到数组中的最小元素。
-
class Solution {
public int findMin(int[] nums) {
int len = nums.length;
int left =0, right = nums.length-1;
while(left<right){
int mid = (left+right)/2;
if(nums[mid]<nums[right]){
right=mid;
}else{
left=mid+1;
}
}
return nums[left];
}
}
2.68 寻找两个正序数组当中的中位数
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
解题思路:
这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们使用一个小trick,我们分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。加入 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。
这里我们需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。然后来处理一些边界问题,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果K=1的话,那么我们只要比较nums1和nums2的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,对K二分,意思是我们需要分别在nums1和nums2中查找第K/2个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第K/2个数字,所以我们需要先检查一下,数组中到底存不存在第K/2个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第K/2个数字,那么我们就淘汰另一个数字的前K/2个数字即可。有没有可能两个数组都不存在第K/2个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的m+n的中间值,所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦,比较这两个数组的第K/2小的数字midVal1和midVal2的大小,如果第一个数组的第K/2个数字小的话,那么说明我们要找的数字肯定不在nums1中的前K/2个数字,所以我们可以将其淘汰,将nums1的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归。反之,我们淘汰nums2中的前K/2个数字,并将nums2的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归即可。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length, len2 = nums2.length;
int left = (len1+len2+1)/2;
int right = (len1+len2+2)/2;
return (findKthNum(nums1, nums2, 0,0, left)+ findKthNum(nums1, nums2, 0,0, right))/2.0;
}
private int findKthNum(int []nums1, int[]nums2, int i, int j, int k){
if(i>=nums1.length){
return nums2[j+k-1];
}
if(j>=nums2.length){
return nums1[i+k-1];
}
if(k==1){
return Math.min(nums1[i], nums2[j]);
}
int minVal1 = (i+k/2-1>=nums1.length? Integer.MAX_VALUE:nums1[i+k/2-1]);
int minVal2 = (j+k/2-1>=nums2.length? Integer.MAX_VALUE:nums2[j+k/2-1]);
if(minVal1<minVal2){
return findKthNum(nums1, nums2, i+k/2,j, k-k/2);
}else{
return findKthNum(nums1, nums2, i, j+k/2, k-k/2);
}
}
}
栈
2.69 有效括号
class Solution {
public boolean isValid(String s) {
LinkedList<Character>stack=new LinkedList<>();
for(char c:s.toCharArray()){
if(c=='['||c=='('||c=='{'){
stack.push(c);
}else{
if(stack.isEmpty()){
return false;
}
if(c==')'){
if(stack.peek()=='('){
stack.pop();
}else{
return false;
}
}else if (c==']'){
if(stack.peek()=='['){
stack.pop();
}else{
return false;
}
}else if(c=='}'){
if(stack.peek()=='{'){
stack.pop();
}else{
return false;
}
}
}
}
return stack.isEmpty();
}
}
2.70 最小栈
这里的最小栈比较简单,就不写题解了(嘿嘿,偷下懒)
class MinStack {
LinkedList<Integer>stack=new LinkedList<>();
PriorityQueue<Integer>pq=new PriorityQueue<>();
public MinStack() {
}
public void push(int val) {
pq.add(val);
stack.push(val);
}
public void pop() {
pq.remove(stack.pop());
}
public int top() {
return stack.peek();
}
public int getMin() {
return pq.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
2.71 字符串解码
主要思路:遍历字符串当遇到的字符是数字的时候更新k的数值,如果是左括号[,也就说明我们需要先解码括号里面的,所以分别利用两个栈去保存之前解码过程当中的结果。当遇到右括号的时候说明我们已经把括号内的解码完成了,所以需要考虑之前的解码结果,但是这里还有个问题,比如如果字符串是abc2[abc],要考虑放将括号内的字符串重复2遍,也就是在之前的结果后面追加两次
class Solution {
public String decodeString(String s) {
StringBuffer strBuff = new StringBuffer();
int k =0;
LinkedList<StringBuffer>strStack = new LinkedList<>();
LinkedList<Integer>numStack = new LinkedList<>();
for(char c : s.toCharArray()){
if(Character.isDigit(c)){
k = k*10+c-'0';
}else{
if (c =='['){
strStack.push(strBuff);
numStack.push(k);
strBuff=new StringBuffer();
k = 0;
}else if (c==']'){
StringBuffer preBuff = strStack.pop();
int num = numStack.pop();
for(int i =0; i< num;i++){
preBuff.append(strBuff);
}
strBuff = preBuff;
k =0;
}else{
strBuff.append(c);
}
}
}
return strBuff.toString();
}
}
2.72 每日温度
这里主要是利用的栈,入栈的元素都是从大到小进行排列的,当遍历到的元素的大小大于栈顶的元素则出栈。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len = temperatures.length;
int [] res = new int [len];
LinkedList<Integer>stack=new LinkedList<>();
for(int i=0;i<len;i++){
while(!stack.isEmpty()&&temperatures[stack.peek()]<temperatures[i]){
res[stack.peek()]=i-stack.pop();
}
stack.push(i);
}
return res;
}
}
2.73 柱状图中最大的矩形
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length; // 获取原数组的长度
int[] a = new int[len + 2]; // 创建一个新数组,长度比原数组多2,用于添加哨兵节点
for (int i = 0; i < len; i++) {
a[i + 1] = heights[i]; // 将原数组复制到新数组的中间位置,首尾保留为0(哨兵)
}
LinkedList<Integer> stack = new LinkedList<>(); // 使用链表实现的栈来保存索引
stack.push(-1); // 初始压入-1作为左边界哨兵,简化边界判断
int res = 0; // 初始化结果为0,用于保存最大面积
for (int i = 0; i < a.length; i++) { // 遍历新数组的所有元素(包括哨兵)
// 当栈顶元素对应的高度大于当前元素时,弹出并计算面积
while (!stack.isEmpty() && stack.peek() >= 0 && a[stack.peek()] > a[i]) {
int height = a[stack.pop()]; // 弹出栈顶索引,获取对应的高度
// 计算宽度:当前索引i - 新栈顶索引(左边界) - 1
int width = i - stack.peek() - 1;
res = Math.max(res, height * width); // 更新最大面积
}
stack.push(i); // 将当前索引压入栈,维护单调递增栈
}
return res; // 返回最大面积
}
}
堆
2.74 数组中的第k个最大的元素
215. 数组中的第K个最大元素 - 力扣(LeetCode)
「快速选择」:每次只在目标所在的分区递归,避免完全排序。
使用双指针从两端向中间扫描,将数组划分为两部分:
-
左半部分 ≤ pivot(基准值)
-
右半部分 ≥ pivot
class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
// 将第k大转换为第(n-k)小的元素,快速选择算法
return quickSelect(nums, 0, len-1, len - k);
}
// 快速选择算法核心实现
private int quickSelect(int[] nums, int left, int right, int k) {
// 终止条件:当搜索范围缩小到单个元素时直接返回
if (left == right) {
return nums[k];
}
// 选择基准值(固定选择左端点)
int target = nums[left];
// Hoare分区方案初始化双指针
int i = left; // 左指针起始位置
int j = right; // 右指针起始位置
// 调整指针初始位置(越界状态)
i--; // 初始化为 left-1
j++; // 初始化为 right+1
// 分区循环:将数组划分为 <=target 和 >=target 的两部分
while (i < j) {
// 双指针向中间移动(先移动再判断)
i++; // 左指针右移
j--; // 右指针左移
// 左指针寻找 >= target 的元素
while (nums[i] < target) {
i++;
}
// 右指针寻找 <= target 的元素
while (nums[j] > target) {
j--;
}
// 交换破坏顺序的元素对
if (i < j) {
swap(nums, i, j);
}
}
// 递归方向选择:根据k的位置决定处理哪个分区
if (k <= j) {
// 目标在左半区 [left, j]
return quickSelect(nums, left, j, k);
} else {
// 目标在右半区 [j+1, right]
return quickSelect(nums, j + 1, right, k);
}
}
// 辅助函数:交换数组元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
快排思路:
这里有俩要注意的点,就是在分区的时候是i<=j
条件 i <= j
是用于确保在 i
和 j
未相遇时继续交换元素。这是为了确保在所有需要交换的元素被交换后,i
和 j
正好定位在应该分区的位置。如果将条件 i <= j
换成 i < j
,当 i == j
时循环会终止,但这有可能导致没有交换最后的需要交换的两个元素,从而影响最终排序结果
举例:
假设我们有一个数组:[3, 2, 1, 5, 6, 4]
,并且希望能够使用快速排序中的 partition
方法来分区这个数组。设定 right
指针指向数组的最后一个元素,所以目标值 target
为 4
。
我们可以追踪 i
和 j
指针的移动:
-
初始状态:
i=0
,j=4
,target=4
,数组为[3, 2, 1, 5, 6, 4]
-
j
向左移动,直到指向一个小于4
的元素,此时j=2
. -
i
向右移动,直到指向一个大于4
的元素,此时i=3
. -
交换
i
和j
指向的元素。数组变为[3, 2, 1, 4, 6, 5]
. -
i
继续向右,j
继续向左,直到i=3
,j=2
.
在第4步交换的过程中,i
正好等于 j
,如果使用条件 i<=j
,则可以交换元素。但是如果使用 i<j
,交换会跳过,导致算法无法正确地把元素移动到适合的位置。
class Solution {
public int findKthLargest(int[] nums, int k) {
quickSort(nums, 0, nums.length-1);
return nums[nums.length-k];
}
private void quickSort(int[] nums, int left, int right){
if(left<right){
int partitionIndex = partition(nums, left, right);
quickSort(nums, left, partitionIndex-1);
quickSort(nums, partitionIndex+1, right);
}
}
private int partition(int []nums, int left, int right){
int target = nums[right], i=left, j=right-1;
while(i<=j){
while(i<=j&&nums[j]>target){
j--;
}
while(i<=j&&nums[i]<target){
i++;
}
if(i<=j){
swap(nums,i,j);
i++;
j--;
}
}
swap(nums,i, right);
return i;
}
private void swap(int[]nums, int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
2.75 前k个高频元素
这里的主要思路就是先用map集合统计每一个数字的频率,然后再用优先队列对map当中的key---value进行排序,然后取出来前k个即可。
class Solution {
Map<Integer, Integer>map=new HashMap<>();
public int[] topKFrequent(int[] nums, int k) {
for(int num:nums){
map.put(num, map.getOrDefault(num,0)+1);
}
PriorityQueue<int[]>pq=new PriorityQueue<>((o1,o2)->o2[1]-o1[1]);
Set<Integer>keys=map.keySet();
for(int key:keys){
pq.offer(new int []{key, map.get(key)});
}
List<Integer>res=new ArrayList<>();
while(k-->0){
res.add(pq.poll()[0]);
}
return res.stream().mapToInt(i->i).toArray();
}
}
2.76 数据流的中位数
用一个小顶堆和大顶堆。小顶堆存放较大的一半,大顶堆存放较小的一半
class MedianFinder {
PriorityQueue<Integer>small_stack=new PriorityQueue<>();
PriorityQueue<Integer>big_stack=new PriorityQueue<>((o1,o2)->o2-o1);
public MedianFinder() {
}
public void addNum(int num) {
if(small_stack.size()==big_stack.size()){
big_stack.offer(num);
small_stack.offer(big_stack.poll());
}else{
small_stack.offer(num);
big_stack.offer(small_stack.poll());
}
}
public double findMedian() {
return big_stack.size()==small_stack.size()? (big_stack.peek()+small_stack.peek())/2.0: small_stack.peek();
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
贪心算法
2.77 买卖股票的最佳时期
选择价格最低的交易日买入,选择利润最高的交易日卖出
class Solution {
public int maxProfit(int[] prices) {
int res =0;
int minValue =prices[0];
for(int i=0;i<prices.length;i++){
minValue=Math.min(prices[i], minValue);
res=Math.max(res, prices[i]-minValue);
}
return res;
}
}
2.78 跳跃游戏
这个题目有两个比较重要的点:
-
每走一步就记录最远可以到哪里(这里的最远是从坐标i可以到达的最远)
-
最远可达位置够不够你继续往前走
class Solution {
public boolean canJump(int[] nums) {
int coverage=0;
for(int i=0;i<=coverage;i++){
coverage=Math.max(coverage, nums[i]+i);
if(coverage>=nums.length-1){
return true;
}
}
return false;
}
}
2.79 跳跃游戏II
-
每次更新
maxCoverage
为当前下标i
加上nums[i]
的值(即从i
出发能跳到的最远位置)和原有maxCoverage
的较大者。 -
当遍历到
currentCoverage
时,说明已到达当前跳跃的边界,必须进行一次新的跳跃(count++
),并将currentCoverage
更新为最新的maxCoverage
。
class Solution {
public int jump(int[] nums) {
int currentCoverage=0;
int maxCoverage=0;
int count=0;
if(nums.length<=1){
return 0;
}
for(int i=0;i<nums.length;i++){
maxCoverage=Math.max(maxCoverage, nums[i]+i);
if(currentCoverage==i){
currentCoverage=maxCoverage;
count++;
if(maxCoverage>=nums.length-1){
return count;
}
}
}
return count;
}
}
2.80 划分字母区间
这里的做法是先借助一个数组来统计一下字符出现的最远的位置是在哪里,比如s = "ababcbacadefegdehijhklij",这里的a最远的位置是在i=8。然后再遍历字符串,动态更新endIndex,当endIndx==i的时候说明已经到了可以切分的边界了。
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer>res= new ArrayList<>();
int len = s.length();
int []index =new int [128];
for(int i=0;i<len;i++){
index[s.charAt(i)]=i;
}
int startIndex =0;
int endIndex =0;
for(int i=0;i<s.length();i++){
endIndex=Math.max(endIndex, index[s.charAt(i)]);
if(endIndex==i){
res.add(endIndex-startIndex+1);
startIndex=i+1;
}
}
return res;
}
}
动态规划
2.81 爬楼梯
class Solution {
public int climbStairs(int n) {
int[]dp=new int [n+1];
dp[0]=dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
2.82 杨辉三角
class Solution {
List<List<Integer>>res = new ArrayList<>();
List<Integer>path=new ArrayList<>();
public List<List<Integer>> generate(int numRows) {
int [][]dp=new int [numRows][numRows];
for(int i=0;i<numRows;i++){
dp[i][0]=1;
}
path.add(1);
res.add(new ArrayList(path));
for(int i=1;i<numRows;i++){
path=new ArrayList<>();
path.add(1);
for(int j=1;j<=i;j++){
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
path.add(dp[i][j]);
}
res.add(new ArrayList(path));
}
return res;
}
}
2.83 打家劫舍
class Solution {
public int rob(int[] nums) {
int len =nums.length;
int []dp=new int [len+1];
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];
}
}
2.84 完全平方数
这个是完全背包问题,背包容量是n,物品的话就是完全平方数。外层背包,内层物品的遍历顺序
class Solution {
public int numSquares(int n) {
int []dp=new int [n+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j*j<=i;j++){
dp[i]=Math.min(dp[i], dp[i-j*j]+1);
}
}
return dp[n];
}
}
2.85 零钱兑换
这个是完全背包问题,外层背包,内层物品的遍历顺序
class Solution {
public int coinChange(int[] coins, int amount) {
int []dp=new int [amount+1];
Arrays.fill(dp, 100000);
dp[0]=0;
for(int i=0;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(coins[j]<=i){
dp[i]=Math.min(dp[i], dp[i-coins[j]]+1);
}
}
}
return dp[amount]==100000? -1:dp[amount];
}
}
2.86 单词拆分
这个题目是完全背包问题的变形,s可以看成背包容量,wordDicit可以看成是物品,完全背包的话就是外层背包,内层物品,从大到小遍历就行.一定不要忘记初始化!!
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int len =s.length();
boolean []dp=new boolean[len+1];
dp[0]=true;
for(int i=1;i<=len;i++){
for(int j=0;j<i;j++){
if(wordDict.contains(s.substring(j,i))&&dp[j]){
dp[i]=true;
}
}
}
return dp[len];
}
}
2.87 最长递增子序列
-
初始化:
-
首先获取输入数组
nums
的长度len
。 -
创建一个数组
dp
,长度与nums
相同,用于存储以每个元素为结尾的最长递增子序列的长度。 -
使用
Arrays.fill(dp, 1)
将dp
数组的所有元素初始化为1,因为每个元素本身可以看作是长度为1的递增子序列。 -
初始化结果变量
res
为0,用于存储最长递增子序列的长度。
-
-
动态规划:
-
使用双重循环遍历数组
nums
。外层循环变量i
表示当前考察的元素,内层循环变量j
用于考察i
之前的元素。 -
在内层循环中,检查
nums[i]
是否大于nums[j]
。如果是,则说明可以将nums[i]
接在以nums[j]
结尾的递增子序列后面。 -
更新
dp[i]
的值为dp[j] + 1
和dp[i]
的较大值,即dp[i] = Math.max(dp[i], dp[j] + 1)
。这一步是为了找到以nums[i]
结尾的最长递增子序列。
-
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
int[]dp=new int [len];
int res =0;
Arrays.fill(dp,1);
for(int i=0;i<len;i++){
for(int j=0;j<=i;j++){
if(nums[i]>nums[j]){
dp[i]=Math.max(dp[i], dp[j]+1);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}
2.88 乘积最大的子数组
-
循环逻辑:在循环中,
maxNum
和minNum
分别保存当前子数组的最大和最小乘积。由于乘积可能会因为负数而导致最大值和最小值互换,因此需要同时更新这两个值。 -
临时变量
temp
:使用temp
来保存maxNum
的旧值是必要的,因为在更新maxNum
后,minNum
的计算仍然需要使用旧的maxNum
值。
class Solution {
public int maxProduct(int[] nums) {
int maxNum = nums[0];
int minNum = nums[0];
int res = nums[0];
for(int i=1;i<nums.length;i++){
int temp=maxNum;
maxNum=Math.max(Math.max(nums[i], maxNum*nums[i]), minNum*nums[i]);
minNum=Math.min(Math.min(nums[i], temp*nums[i]), minNum*nums[i]);
res = Math.max(maxNum, res);
}
return res;
}
}
2.89 分割等和子集
这个是01背包问题,物品只能用一次,所以外层物品内层背包,同时背包从大到小进行遍历
如果是完全背包问题,则外层背包,内层物品,背包的顺序不做要求(这两个一定要记住,后面可以秒杀很多背包问题)
class Solution {
public boolean canPartition(int[] nums) {
int sum = Arrays.stream(nums).sum();
if (sum%2!=0){
return false;
}
int target = sum /2;
int []dp=new int [target+1];
for(int i=0;i<nums.length;i++){
for(int j=target;j>=nums[i];j--){
dp[j]=Math.max(dp[j], dp[j-nums[i]]+nums[i]);
}
}
return dp[target]==target? true:false;
}
}
2.90 最长有效括号
-
初始化栈:
-
使用一个栈来存储括号的索引。
-
初始时,栈中放入
-1
,表示一个虚拟的起始位置,以便处理边界条件。
-
-
遍历字符串:
-
对于每个字符,如果是左括号
'('
,将其索引压入栈。 -
如果是右括号
')'
,弹出栈顶元素,表示匹配了一个左括号。
-
-
处理匹配后的情况:
-
如果栈为空,说明当前右括号没有匹配的左括号,将当前索引压入栈,作为新的起始位置。
-
如果栈不为空,计算当前有效括号子串的长度,即当前索引减去栈顶元素的索引,并更新结果
res
。
-
class Solution {
public int longestValidParentheses(String s) {
LinkedList<Integer>stack =new LinkedList<>();
stack.push(-1);
int res =0;
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='('){
stack.push(i);
}else{
stack.pop();
if(stack.isEmpty()){
stack.push(i);
}else{
res=Math.max(res, i-stack.peek());
}
}
}
return res;
}
}
多维动态规划
2.91 不同路径
-
dp[i][j]
表示从起点(1, 1)
到位置(i, j)
的不同路径数。 -
dp[0][1] = 1
是为了方便计算,使得dp[1][1]
可以直接等于 1,因为从起点到起点只有一种路径。 -
对于每个位置
(i, j)
,可以从上方(i-1, j)
或左方(i, j-1)
到达。 -
因此,
dp[i][j]
等于从上方到达的路径数加上从左方到达的路径数,即dp[i-1][j] + dp[i][j-1]
。
class Solution {
public int uniquePaths(int m, int n) {
int [][]dp=new int [m+1][n+1];
dp[0][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
}
2.92 最小路径和
-
初始化动态规划数组:
-
dp[i][j]
表示从起点到位置(i, j)
的最小路径和。 -
m
和n
分别表示网格的行数和列数。
-
-
处理第一列:
-
从起点开始,逐行累加每个位置的值,填充
dp
数组的第一列。 -
这样可以确保从起点到每个位置
(i, 0)
的路径和是正确的。
-
-
处理第一行:
-
从起点开始,逐列累加每个位置的值,填充
dp
数组的第一行。 -
这样可以确保从起点到每个位置
(0, i)
的路径和是正确的。
-
-
填充剩余的
dp
数组:-
对于每个位置
(i, j)
,可以从上方(i-1, j)
或左方(i, j-1)
到达。 -
选择这两个位置中的最小路径和,加上当前网格位置的值
grid[i][j]
,得到dp[i][j]
的值。
-
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
int [][]dp=new int [m][n];
int sum =0;
for(int i=0;i<m;i++){
sum+=grid[i][0];
dp[i][0]=sum;
}
sum=0;
for(int i=0;i<n;i++){
sum+=grid[0][i];
dp[0][i]=sum;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=Math.min(dp[i-1][j], dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
}
2.93 最长回文子串
如果要是想得到i到j是回文子串的话那i+1到j-1也应该是回文子串。换句话讲是要用dp[i+1][j-1]推到出来dp[i][j],画个图其实也就是从左下角推右上角的一个过程。所以i是从len到1,j是从1到i
class Solution {
public String longestPalindrome(String s) {
int len =s.length();
boolean[][]dp=new boolean[len][len];
String res = s.charAt(0)+"";
for(int i = len-1;i>=0;i--){
for(int j=i;j<len;j++){
if(s.charAt(i)==s.charAt(j)&&(j-i+1<=2||dp[i+1][j-1])){
dp[i][j]=true;
if(res.length()<j-i+1){
res=s.substring(i,j+1);
}
}
}
}
return res;
}
}
2.94 最长公共子序列
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int len1 = text1.length();
int len2 = text2.length();
int [][]dp=new int [len1+1][len2+1];
int res =0;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(text1.charAt(i-1)==text2.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]);
}
res=Math.max(res, dp[i][j]);
}
}
return res;
}
}
2.95 编辑距离
-
如果
word1[i-1]
等于word2[j-1]
,则dp[i][j] = dp[i-1][j-1]
,表示不需要额外操作。 -
如果
word1[i-1]
不等于word2[j-1]
,则dp[i][j]
为以下三种操作的最小值加1:-
dp[i-1][j] + 1
:删除word1[i-1]
。 -
dp[i][j-1] + 1
:插入word2[j-1]
。 -
dp[i-1][j-1] + 1
:替换word1[i-1]
为word2[j-1]
。
-
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int [][]dp=new int [len1+1][len2+1];
for(int i=0;i<=len1;i++){
dp[i][0]=i;
}
for(int i=0;i<=len2;i++){
dp[0][i]=i;
}
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i][j-1], dp[i-1][j-1]))+1;
}
}
}
return dp[len1][len2];
}
}
技巧
2.96 只出现一次的数字
使用异或操作
class Solution {
public int singleNumber(int[] nums) {
int res =0;
for(int num:nums){
res^=num;
}
return res;
}
}
2.97 多数元素
-
对数组中的每个元素进行遍历。
-
如果计数器
count
为0,则将当前元素设置为候选元素currentNum
。 -
如果当前元素与候选元素相同,则计数器
count
加1;否则计数器count
减1。
class Solution {
public int majorityElement(int[] nums) {
int count =0;
int currentNum =0;
for(int num:nums){
if (count==0){
currentNum=num;
}
count+=(currentNum==num? 1:-1);
}
return currentNum;
}
}
2.98 颜色分类
-
第一次遍历:
-
从数组的开始位置遍历整个数组,将所有的0移动到数组的前面部分。
-
使用一个指针
point
来记录下一个0应该放置的位置,每次遇到0时,将其与point
位置的元素交换,并将point
向后移动一位。
-
-
第二次遍历:
-
从
point
位置开始遍历剩余的数组,将所有的1移动到数组的中间部分。 -
同样使用
point
指针来记录下一个1应该放置的位置,每次遇到1时,将其与point
位置的元素交换,并将point
向后移动一位。
-
class Solution {
public void sortColors(int[] nums) {
int point =0;
for(int i=0;i<nums.length;i++){
if(nums[i]==0){
swap(nums, point++, i);
}
}
for(int i=point;i<nums.length;i++){
if(nums[i]==1){
swap(nums, point++, i);
}
}
}
private void swap(int[]nums,int i, int j){
int temp =nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
2.99 下一个排列
思路:
-
寻找第一个下降点:
-
从数组的末尾开始,找到第一个
nums[i] > nums[i-1]
的位置,记为p
。这个位置是从右向左第一个下降的点。 -
如果没有找到这样的点(即整个数组是非递增的),则说明当前排列是字典序最大的排列。此时,直接将整个数组反转,得到字典序最小的排列。
-
-
寻找要交换的元素:
-
从数组的末尾开始,找到第一个大于
nums[p]
的元素,记为nums[i]
。 -
交换
nums[p]
和nums[i]
。
-
-
反转后续部分:
- 反转
p+1
到数组末尾的部分,使其成为升序排列,从而得到下一个字典序排列。
- 反转
class Solution {
public void nextPermutation(int[] nums) {
int p = -1;
int len = nums.length;
for(int i=len-1;i>0;i--){
if(nums[i]>nums[i-1]){
p =i-1;
break;
}
}
if(p==-1){
reverse(nums, 0, len-1);
return;
}
for(int i=len-1;i>p;i--){
if(nums[i]>nums[p]){
swap(nums, i, p);
break;
}
}
reverse(nums, p+1, len-1);
return;
}
private void swap(int []nums, int i, int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
private void reverse(int []nums, int startIndex, int endIndex){
int i=startIndex, j=endIndex;
while(i<j){
swap(nums, i++, j--);
}
}
}
100. 寻找重复数
使用“快慢指针”方法,将数组视为一个链表,其中每个元素指向下一个元素的索引。由于数组中有重复元素,这个链表会形成一个环。通过快慢指针,我们可以找到这个环的入口,也就是重复元素。
-
初始化快慢指针:
-
slow
指针从数组的第一个元素开始,即slow = nums[0]
。 -
fast
指针从数组的第二个元素开始,即fast = nums[nums[0]]
。
-
-
寻找环的相遇点:
-
通过循环移动
slow
和fast
指针,直到它们相遇。slow
每次移动一步,而fast
每次移动两步。 -
当
slow
和fast
相遇时,说明存在环。
-
-
寻找环的入口:
-
将
fast
指针重新设置为数组的第一个元素,即fast = 0
。 -
通过循环移动
slow
和fast
指针,每次都移动一步,直到它们再次相遇。 -
相遇点即为环的入口,也就是重复元素。
-
class Solution {
public int findDuplicate(int[] nums) {
int slow =nums[0], fast=nums[nums[0]];
while(slow!=fast){
slow=nums[slow];
fast=nums[nums[fast]];
}
fast=0;
while(fast!=slow){
slow=nums[slow];
fast=nums[fast];
}
return fast;
}
}
3. 补充算法
1. 重排链表
这道题两个思路,一个是借助列表的有序性,用双指针做。
另一个思路是先找到中间节点,然后翻转后面一半的节点,再合并
/**
* Definition for singly-linked list
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public void reorderList(ListNode head) {
if(head==null){
return;
}
List<ListNode>list = new ArrayList<>();
ListNode p = head;
while(p!=null){
list.add(p);
p=p.next;
}
int i=0, j=list.size()-1;
while(i<j){
list.get(j).next=list.get(i).next;
list.get(i).next=list.get(j);
i++;
j--;
}
list.get(i).next=null;
}
}
class Solution {
public void reorderList(ListNode head) {
if(head==null){
return;
}
ListNode mid = findMidNode(head);
ListNode l1=head;
ListNode l2=mid.next;
mid.next=null;
l2=reverseList(l2);
mergeList(l1,l2);
}
public ListNode findMidNode(ListNode head){
ListNode slow=head;
ListNode fast=head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
public ListNode reverseList(ListNode head){
ListNode dummpy =new ListNode(0,head);
ListNode g=dummpy,p=dummpy.next;
while(p!=null&&p.next!=null){
ListNode temp=p.next;
p.next=temp.next;
temp.next=g.next;
g.next=temp;
}
return dummpy.next;
}
public ListNode mergeList(ListNode l1, ListNode l2){
ListNode dummpy = new ListNode();
ListNode p=dummpy;
while(l1!=null||l2!=null){
if(l1!=null){
p.next=l1;
p=p.next;
l1=l1.next;
}
if(l2!=null){
p.next=l2;
p=p.next;
l2=l2.next;
}
}
return dummpy.next;
}
}
3.2 找到k个最接近的元素
658. 找到 K 个最接近的元素 - 力扣(LeetCode)
class Solution {
public List<Integer> findClosestElements(int[] arr, int k, int x) {
int size =arr.length;
int left =0;
int right =size -1;
int removeNums = size -k;
while(removeNums>0){
if(x-arr[left]<=arr[right]-x){
right--;
}else{
left++;
}
removeNums--;
}
List<Integer>res = new ArrayList<>();
for(int i=left;i<=right;i++){
res.add(arr[i]);
}
return res;
}
}
3.3 两个线程交替打印1-100
这个思路倒都很多了,这里只举一个例子哈
public class AlternatePrinting {
private static final Object lock = new Object();
private static int count = 1;
private static final int MAX_COUNT = 100;
public static void main(String[] args) {
Thread t1 = new Thread(new Printer(), "Thread-1");
Thread t2 = new Thread(new Printer(), "Thread-2");
t1.start();
t2.start();
}
static class Printer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (lock) {
if (count > MAX_COUNT) {
break;
}
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notify(); // 唤醒另一个线程
try {
if (count <= MAX_COUNT) {
lock.wait(); // 当前线程等待
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
}
3.4 字符串转化
这个题目好像没有leetcode原题,如果有比较好的思路可以讨论下
规定1和A对应、2和B对应、3和C对应...26和Z对应,那么一个数字字符串比如"111”就可以转化为:"AAA"、"KA"和"AK"。给定一个只有数字字符组成的字符串str,请问有多少种转化结果?
动态规划思路
-
定义状态:
- 用
dp[i]
表示字符串前i
个字符可以转换的组合数。
- 用
-
状态转移方程:
-
如果第
i
个字符(从1开始计数)是有效的单字符编码(即不为 '0'),那么dp[i] += dp[i-1]
。 -
如果第
i-1
和第i
个字符组成的双字符编码在 "10" 到 "26" 之间,那么dp[i] += dp[i-2]
。
-
-
初始化:
-
dp[0] = 1
,表示空字符串有一种解码方式。 -
dp[1]
取决于第一个字符是否为 '0',如果是 '0',则dp[1] = 0
,否则dp[1] = 1
。
-
-
计算结果:
- 最终结果是
dp[n]
,其中n
是字符串的长度。
- 最终结果是
public class Solution {
public int numDecodings(String s) {
if (s == null || s.length() == 0 || s.charAt(0) == '0') {
return 0;
}
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1; // 空字符串有一种解码方式
dp[1] = s.charAt(0) != '0' ? 1 : 0; // 第一个字符不为 '0' 才有一种解码方式
for (int i = 2; i <= n; i++) {
int oneDigit = Integer.parseInt(s.substring(i - 1, i));
int twoDigits = Integer.parseInt(s.substring(i - 2, i));
if (oneDigit >= 1) {
dp[i] += dp[i - 1];
}
if (twoDigits >= 10 && twoDigits <= 26) {
dp[i] += dp[i - 2];
}
}
return dp[n];
}
}
3.5 克隆N叉树
这里主要用到的是递归的思路,因为是N叉树也就是孩子节点的个数不确定,所以在定义树的时候要用到List的结构去定义
import java.util.ArrayList;
import java.util.List;
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
children = new ArrayList<>();
}
public Node(int _val, ArrayList<Node> _children) {
val = _val;
children = _children;
}
}
public class CloneNaryTree {
public Node cloneTree(Node root) {
if (root == null) {
return null;
}
Node newRoot = new Node(root.val);
for (Node child : root.children) {
//递归调用
newRoot.children.add(cloneTree(child));
}
return newRoot;
}
public static void main(String[] args) {
// Example usage:
Node root = new Node(1);
root.children.add(new Node(2));
root.children.add(new Node(3));
root.children.add(new Node(4));
CloneNaryTree cloner = new CloneNaryTree();
Node clonedRoot = cloner.cloneTree(root);
printTree(clonedRoot);
}
public static void printTree(Node root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
for (Node child : root.children) {
printTree(child);
}
}
}
3.6 带过期时间的LRU
这个是LeetCode的变体146. LRU 缓存 - 力扣(LeetCode)
主要思路和原题类似,只不过会在每个节点中添加一个时间戳,并在每次访问或插入时检查节点是否过期
import java.util.HashMap;
import java.util.Map;
class DLinkedNode {
DLinkedNode prev;
DLinkedNode next;
int value;
int key;
long expireTime; // 过期时间
public DLinkedNode() {}
public DLinkedNode(int key, int value, long expireTime) {
this.key = key;
this.value = value;
this.expireTime = expireTime;
}
}
class LRUCache {
private Map<Integer, DLinkedNode> map = new HashMap<>();
private DLinkedNode head = new DLinkedNode();
private DLinkedNode tail = new DLinkedNode();
private int capacity;
private int size = 0;
private long defaultExpireTime; // 默认过期时间
public LRUCache(int capacity, long defaultExpireTime) {
this.capacity = capacity;
this.defaultExpireTime = defaultExpireTime;
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
DLinkedNode node = map.get(key);
if (isExpired(node)) {
remove(node);
map.remove(key);
size--;
return -1;
}
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
long expireTime = System.currentTimeMillis() + defaultExpireTime;
DLinkedNode node = new DLinkedNode(key, value, expireTime);
if (!map.containsKey(key)) {
insert(node);
map.put(key, node);
size++;
if (size > capacity) {
DLinkedNode temp = tail.prev;
map.remove(temp.key);
remove(temp);
size--;
}
} else {
DLinkedNode existingNode = map.get(key);
if (isExpired(existingNode)) {
remove(existingNode);
size--;
} else {
remove(existingNode);
}
map.put(key, node);
insert(node);
}
}
private boolean isExpired(DLinkedNode node) {
return System.currentTimeMillis() > node.expireTime;
}
private void moveToHead(DLinkedNode node) {
remove(node);
insert(node);
}
private void remove(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void insert(DLinkedNode node) {
node.next = head.next;
head.next.prev = node;
node.prev = head;
head.next = node;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity, defaultExpireTime);
* int param_1 = obj.get(key);
* obj.put(key, value);
*/
3.7 n个数的最大公约数
这里主要是用辗转相除法
public class GCDCalculator {
public static void main(String[] args) {
int[] numbers = {48, 64, 256, 1024};
int gcd = findGCD(numbers);
System.out.println("The GCD of the given numbers is: " + gcd);
}
public static int findGCD(int[] numbers) {
int result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
result = gcd(result, numbers[i]);
}
return result;
}
public static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
3.8 大数相加
大数的话一般都要用到字符串来做
public class BigNumberAddition {
public static void main(String[] args) {
String num1 = "123456789012345678901234567890";
String num2 = "987654321098765432109876543210";
String result = addBigNumbers(num1, num2);
System.out.println("The sum of the given numbers is: " + result);
}
public static String addBigNumbers(String num1, String num2) {
// 确保 num1 是较长的那个
if (num1.length() < num2.length()) {
String temp = num1;
num1 = num2;
num2 = temp;
}
// 反转字符串以便从最低位开始相加
StringBuilder sb1 = new StringBuilder(num1).reverse();
StringBuilder sb2 = new StringBuilder(num2).reverse();
StringBuilder result = new StringBuilder();
int carry = 0;
// 遍历每一位进行相加
for (int i = 0; i < sb1.length(); i++) {
int digit1 = sb1.charAt(i) - '0';
int digit2 = i < sb2.length() ? sb2.charAt(i) - '0' : 0;
int sum = digit1 + digit2 + carry;
carry = sum / 10;
result.append(sum % 10);
}
// 如果最后有进位,添加到结果中
if (carry != 0) {
result.append(carry);
}
// 反转结果字符串并返回
return result.reverse().toString();
}
}
3.9 大数相乘
public class BigNumberMultiplication {
public static void main(String[] args) {
String num1 = "12345678901234567890";
String num2 = "98765432109876543210";
String result = multiplyBigNumbers(num1, num2);
System.out.println("The product of the given numbers is: " + result);
}
public static String multiplyBigNumbers(String num1, String num2) {
int len1 = num1.length();
int len2 = num2.length();
int[] result = new int[len1 + len2];
// 逐位相乘
for (int i = len1 - 1; i >= 0; i--) {
for (int j = len2 - 1; j >= 0; j--) {
int digit1 = num1.charAt(i) - '0';
int digit2 = num2.charAt(j) - '0';
int product = digit1 * digit2;
int p1 = i + j;
int p2 = i + j + 1;
int sum = product + result[p2];
result[p2] = sum % 10;
result[p1] += sum / 10;
}
}
// 将结果转换为字符串
StringBuilder sb = new StringBuilder();
for (int num : result) {
if (!(sb.length() == 0 && num == 0)) { // 去掉前导零
sb.append(num);
}
}
return sb.length() == 0 ? "0" : sb.toString();
}
}
3.10 递增的三元子序列
赋初始值的时候,已经满足second > first了,现在找第三个数third
-
如果third比second大,那就是找到了,直接返回true
-
如果third比second小,但是比first大,那就把second指向third,然后继续遍历找third
-
如果third比first还小,那就把first指向third,然后继续遍历找third(这样的话first会跑到second的后边,但是不要紧,因为在second的前边,老first还是满足的)
class Solution {
public boolean increasingTriplet(int[] nums) {
int n = nums.length;
if (n < 3) {
return false;
}
int first = nums[0], second = Integer.MAX_VALUE;
for (int i = 1; i < n; i++) {
int num = nums[i];
if (num > second) {
return true;
} else if (num > first) {
second = num;
} else {
first = num;
}
}
return false;
}
}
3.11 移掉 K 位数字
class Solution {
public String removeKdigits(String num, int k) {
int len = num.length();
if(len==k){
return "0";
}
LinkedList<Character>stack=new LinkedList<>();
for(char c : num.toCharArray()){
while(!stack.isEmpty()&&k>0&&stack.peek()>c){
stack.pop();
k--;
}
stack.push(c);
}
while(k>0){
stack.pop();
k--;
}
StringBuffer strBuff=new StringBuffer();
while(!stack.isEmpty()){
strBuff.append(stack.pop());
}
strBuff.reverse();
while(strBuff.length()>0&&strBuff.charAt(0)=='0'){
strBuff.deleteCharAt(0);
}
if(strBuff.length()==0){
return "0";
}
return strBuff.toString()== "" ? "0" : strBuff.toString();
}
}
3.12 二叉树不相邻节点之和最大值
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public class MaxSumOfNonAdjacentNodes {
public int getMaxSum(TreeNode root) {
int[] result = dfs(root);
return Math.max(result[0], result[1]);
}
private int[] dfs(TreeNode node) {
if (node == null) {
return new int[]{0, 0};
}
int[] left = dfs(node.left);
int[] right = dfs(node.right);
// result[0] is the max sum without including the current node
// result[1] is the max sum including the current node
int[] result = new int[2];
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = node.val + left[0] + right[0];
return result;
}
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.right = new TreeNode(3);
root.right.right = new TreeNode(1);
MaxSumOfNonAdjacentNodes solution = new MaxSumOfNonAdjacentNodes();
System.out.println("Maximum sum of non-adjacent nodes: " + solution.getMaxSum(root));
}
}
3.13 判断A树是B树的子树
这里的话其实主要A树和B树相等,A树是B树的左子树or右子树。用递归就行
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(subRoot==null){
return true;
}
if(root==null){
return false;
}
return isSubtree(root.left, subRoot)||isSubtree(root.right, subRoot)||isSame(root,subRoot);
}
boolean isSame(TreeNode root, TreeNode subRoot){
if(subRoot==null&&root==null){
return true;
}
if(subRoot==null||root==null){
return false;
}
return isSame(root.left, subRoot.left)&&isSame(root.right, subRoot.right)&&root.val==subRoot.val;
}
}
3.14 多叉树的深度
这里和二叉树的深度是类似的
import java.util.ArrayList;
import java.util.List;
// 定义树节点类
class TreeNode {
int val;
List<TreeNode> children;
TreeNode(int val) {
this.val = val;
this.children = new ArrayList<>();
}
}
public class MultiwayTreeDepth {
// 计算多叉树的深度
public static int getDepth(TreeNode root) {
if (root == null) {
return 0;
}
int maxDepth = 0;
for (TreeNode child : root.children) {
maxDepth = Math.max(maxDepth, getDepth(child));
}
return maxDepth + 1;
}
public static void main(String[] args) {
// 创建一个简单的多叉树
TreeNode root = new TreeNode(1);
TreeNode child1 = new TreeNode(2);
TreeNode child2 = new TreeNode(3);
TreeNode child3 = new TreeNode(4);
root.children.add(child1);
root.children.add(child2);
child1.children.add(child3);
// 计算并输出多叉树的深度
int depth = getDepth(root);
System.out.println("The depth of the multiway tree is: " + depth);
}
}
3.15 寻找峰值
class Solution {
public int findPeakElement(int[] nums) {
int left=0,right=nums.length-1;
while(left<right){
int mid=left+(right-left)/2;
if(nums[mid]>nums[mid+1]){
right=mid;
}else{
left=mid+1;
}
}
return left;
}
}
3.16 删除有序数组中的重复项
26. 删除有序数组中的重复项 - 力扣(LeetCode)
class Solution {
public int removeDuplicates(int[] nums) {
int len = nums.length;
int slow=0, fast=0;
while(fast<len){
if(nums[fast]!=nums[slow]){
slow+=1;
int temp =nums[slow];
nums[slow]=nums[fast];
nums[fast]=temp;
}
fast++;
}
return slow+1;
}
}
3.17 寻找小于某一个数的最大排列数
这里还有一个思路是在找对应的元素的时候先对数组排序,然后用二分查找找最高位该插入的位置
eg:现在有一个数字34512,有一个数组[2,3,4,2],题目要求用数组当中的数字找到比34512小数字当中最大的那个
import java.util.Arrays;
public class FindMaxNumber {
public static void main(String[] args) {
int target = 34512;
int[] digits = {2, 3, 4, 2};
int maxNumber = findMaxNumberLessThanTarget(target, digits);
System.out.println("最大的比 " + target + " 小的数字是: " + maxNumber);
}
private static int findMaxNumberLessThanTarget(int target, int[] digits) {
char[] targetChars = String.valueOf(target).toCharArray();
Arrays.sort(digits);
StringBuilder result = new StringBuilder();
boolean found = false;
for (int i = 0; i < targetChars.length; i++) {
int targetDigit = targetChars[i] - '0';
int maxDigit = -1;
for (int digit : digits) {
if (digit < targetDigit) {
maxDigit = digit;
}
}
if (maxDigit == -1) {
result.append(digits[digits.length - 1]);
} else {
result.append(maxDigit);
found = true;
break;
}
}
if (!found) {
return -1; // 如果没有找到比目标小的数字,返回-1
}
for (int i = result.length(); i < targetChars.length; i++) {
result.append(digits[digits.length - 1]);
}
return Integer.parseInt(result.toString());
}
}
3.18 给定一个化学公式, 求指定原子的数量
要能解决嵌套括号的情况.(思路: 使用栈)
样例: input: H(N2O)3求N, output: 6
这个解题思路我是用的hot100当中的https://leetcode.cn/problems/decode-string/思路做的
这里有一些不一样的是碰到数字就要展开,而不是括号,这里展开之后直接再遍历一下展开的字符串就好了,我在这边就不写那一块代码了
public class AtomCounter {
public static void main(String[] args) {
Solution1 solution1 = new Solution1();
String s = solution1.decodeString("Mg(O(H)2)2");
System.out.println(s);
}
}
class Solution1 {
public String decodeString(String s) {
LinkedList<StringBuffer>strStack = new LinkedList<>();
int k=0;
StringBuffer strBuff = new StringBuffer();
boolean flag =false;
for(char c:s.toCharArray()){
if(Character.isDigit(c)){
k = k*10+c-'0';
String str = strBuff.toString();
strBuff=new StringBuffer();
for(int i=0;i<k;i++){
strBuff.append(str);
}
k=0;
if(flag){
StringBuffer preBuff = strStack.pop();
strBuff=preBuff.append(strBuff);
}
}else{
if(c=='('){
strStack.push(strBuff);
strBuff=new StringBuffer();
}else if (c==')'){
flag=true;
}else{
strBuff.append(c);
}
}
}
return strBuff.toString();
}
}
3.19 删除有序链表中所有重复元素
83. 删除排序链表中的重复元素 - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null){
return head;
}
ListNode cur=head;
while(cur.next!=null){
if(cur.val==cur.next.val){
cur.next=cur.next.next;
}else{
cur=cur.next;
}
}
return head;
}
}
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummpy = new ListNode(0, head);
ListNode cur =dummpy;
while(cur.next!=null&&cur.next.next!=null){
if(cur.next.val==cur.next.next.val){
int x = cur.next.val;
while(cur.next!=null&&cur.next.val==x){
cur.next=cur.next.next;
}
}else{
cur=cur.next;
}
}
return dummpy.next;
}
}
3.20 返回单链表倒数第k个节点,只能遍历一次
方法思路
-
初始化指针:设置两个指针
fast
和slow
,均指向链表头节点。 -
快指针先行:快指针
fast
先向前移动k
步。若在此过程中fast
变为null
,说明链表长度不足k
,直接返回null
。 -
同步移动:快慢指针同时移动,直到快指针
fast
到达链表末尾(即fast == null
)。此时,慢指针slow
指向的节点即为倒数第k个节点。
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public ListNode findKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
// 快指针先移动k步
for (int i = 0; i < k; i++) {
if (fast == null) {
return null; // k超过链表长度
}
fast = fast.next;
}
// 快慢指针同步移动,直到快指针到末尾
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}