链表:
难度中等1048
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:输入:head = []
输出:[]
示例 3:输入:head = [1]
输出:[1]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null ||head.next==null){
return head;
}
ListNode res=head;
while(res!=null &&res.next!=null){
int temp=res.val;
res.val=res.next.val;
res.next.val=temp;
res=res.next.next;
}
return head;
}
}
难度简单1122
给你一个单链表的头节点
head
,请你判断该链表是否为回文链表。如果是,返回true
;否则,返回false
。示例 1:
输入:head = [1,2,2,1] 输出:true示例 2:
输入:head = [1,2] 输出:false
思路:将链表中元素值存入数组中,判断数组是否回文就可以了
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> vals = new ArrayList<Integer>();
ListNode currentNode = head;
while (currentNode != null) {
vals.add(currentNode.val);
currentNode = currentNode.next;
}
// 使用双指针判断是否回文
int front = 0;
int back = vals.size() - 1;
while (front < back) {
if (!vals.get(front).equals(vals.get(back))) {
return false;
}
front++;
back--;
}
return true;
}
}
难度中等1567
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。进阶:你能尝试使用一趟扫描实现吗?
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]示例 2:
输入:head = [1], n = 1 输出:[]示例 3:
输入:head = [1,2], n = 1 输出:[1]
思路:定义两个节点,一个fast,一个slow(slow.next=head)先让fast走n步,然后fast和slow同时走,当fast==null停止,slow刚好走到前面那个节点处。这个思路在前面一道题返回链表后面n各元素使用过。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode fast = head;
ListNode slow = dummy;
while(n>0){
fast=fast.next;
n--;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
ListNode ans = dummy.next;
return ans;
}
}
括号问题:
难度简单2655
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()" 输出:true示例 2:
输入:s = "()[]{}" 输出:true示例 3:输入:s = "(]" 输出:false
示例 4:输入:s = "([)]" 输出:false
示例 5:输入:s = "{[]}" 输出:true
思路:使用栈实现,但要注意map存入括号的key,value顺序。
因为我们应该看到右括号),},]是来检查栈顶元素是不是对应的左括号,所以我们应该首先将左括号存入,当遇到右括号时看栈顶元素是不是对应的左括号,而且map.get()方法,得到value比较方便。
class Solution {
public boolean isValid(String s) {
int len=s.length();
if(len%2==1)
return false;
Map<Character,Character> map=new HashMap<>();
map.put(')','(');
map.put('}','{');
map.put(']','[');
Deque<Character> stack=new LinkedList<>();
for(int i=0;i<len;i++){
char c=s.charAt(i);
if(map.containsKey(c)){
if(stack.isEmpty() || map.get(c)!=stack.peek()){
return false;
}
stack.pop();
}else{
stack.push(c);
}
}
return stack.isEmpty();
}
}
括号生成 (一道回溯问题)
难度中等2053
数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。有效括号组合需满足:左括号必须以正确的顺序闭合。
示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]示例 2:
输入:n = 1 输出:["()"]
回溯本质是暴力求解+递归。所以很容易得到递归结束条件,当left和right都为0。(left和right是指可以放多少括号)。
这道题注意递归的执行过程,和剪枝的代码
大概阐述一个过程:
①四次递归字符串变为(((,又三次递归变为((()))。此时程序要结束啦,返回到第三次递归处,字符串变为((,注意而不是变为(((,因为每次字符串修改是在递归中实现的,然后变为(()下一步(()())
②又该结束程序,这是字符串变为了((),然后(())()。下一步结束程序变为(())程序下一步要加),这是进行剪枝了因为left=1,right=0,剪枝后(变为这样,然后()结果为
()(())
③最后一次,程序结束()(是这样的,然后()(),然后()()()结束。
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res=new LinkedList<>();
if(n==0){
return res;
}
dfs("",n,n,res);
return res;
}
private void dfs(String x,int left,int right,List<String> res){
if(left==0 && right==0){
res.add(new String(x));
return;
}
//剪枝
if(left>right){
return;
}
if(left>0){
dfs(x+"(",left-1,right,res);
}
if(right>0){
dfs(x+")",left,right-1,res);
}
}
}
回溯
难度中等1326
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]示例 2:
输入:nums = [0] 输出:[[],[0]]
比较简单,多刷几个回溯题就能看懂了
推荐
全排列,全排列2,子集,子集2,组合,组合2,和括号生产。
看不懂多debug,多想,多画
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res=new LinkedList<>();
int len=nums.length;
if(len==0)
return res;
Deque<Integer> path=new ArrayDeque<>();
dfs(nums,len,0,res,path);
return res;
}
private void dfs(int []nums,int len,int begin, List<List<Integer>> res,Deque<Integer> path){
res.add(new ArrayList<>(path));
for(int i=begin;i<len;i++){
path.add(nums[i]);
dfs(nums,len,i+1,res,path);
path.removeLast();
}
}
}
难度中等652
给你一个整数数组
nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]示例 2:
输入:nums = [0] 输出:[[],[0]]
避免重复,其实和组合2,全排列2相似。
检测重复的代码确实巧妙,通常的做法是先将数组排序,然后在递归的过程中,当前索引i处元素不能等于索引出i-1的元素,同时也要保证不在同一层
引用了Leecode40. 组合总和 II
题解中某位大佬评论
解释语句: if cur > begin and candidates[cur-1] == candidates[cur] 是如何避免重复的。
这个避免重复当思想是在是太重要了。 这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即 1 / \ 2 2 这种情况不会发生 但是却允许了不同层级之间的重复即: / \ 5 5 例2 1 / 2 这种情况确是允许的 / 2 为何会有这种神奇的效果呢? 首先 cur-1 == cur 是用于判定当前元素是否和之前元素相同的语句。这个语句就能砍掉例1。 可是问题来了,如果把所有当前与之前一个元素相同的都砍掉,那么例二的情况也会消失。 因为当第二个2出现的时候,他就和前一个2相同了。 那么如何保留例2呢? 那么就用cur > begin 来避免这种情况,你发现例1中的两个2是处在同一个层级上的, 例2的两个2是处在不同层级上的。 在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中, 必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。 第一个出现的2的特点就是 cur == begin. 第二个出现的2 特点是cur > begin.
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res=new LinkedList<>();
int len=nums.length;
if(len==0)
return res;
Arrays.sort(nums);
Deque<Integer> path=new ArrayDeque<>();
dfs(nums,len,0,path,res);
return res;
}
private void dfs(int []nums,int len,int begin, Deque<Integer> path,List<List<Integer>> res){
res.add(new ArrayList<>(path));
for(int i=begin;i<len;i++){
if(i>begin && nums[i]==nums[i-1]){
continue;
}
path.add(nums[i]);
dfs(nums,len,i+1,path,res);
path.removeLast();
}
}
}