算法入门(LeetCode)——递归/回溯

21. 合并两个有序链表

1. 题目

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
合并两个有序链表

2. 代码
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr) return list2;
		if (list2 == nullptr) return list1;
        if (list1->val < list2->val) {
			list1->next = mergeTwoLists(list1->next, list2);
			return list1;
		} else {
			list2->next = mergeTwoLists(list2->next, list1);
			return list2;
		}
    }
};

206. 反转链表

1. 题目

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

反转链表

2. 代码
ListNode* reverseList(ListNode* head) {
		//双指针
//		ListNode* cur = head;
//		ListNode* pre = nullptr;
//		while (cur != nullptr) {
//			ListNode* tmp = cur->next;
//			cur->next = pre;
//			pre = cur;
//			cur = tmp;
//		}
//		return pre;
		
		// 递归
		if (head == NULL || head->next == NULL) {
			//直到当前节点的下一个节点为空时返回当前节点
            //由于5没有下一个节点了,所以此处返回节点5
            return head;
        }
        //递归传入下一个节点,目的是为了到达最后一个节点
        ListNode* node = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return node;
    }
3. 题解

双指针:

  • 将每个结点的后一个结点放置在当前结点的前一个结点

递归:
(题解来自)

  • 第一轮出栈,head为5,head.next为空,返回5
  • 第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
    把当前节点的子节点的子节点指向当前节点
    此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
    此时链表为1->2->3->4<-5
    返回节点5
  • 第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
    此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
    此时链表为1->2->3<-4<-5
    返回节点5
  • 第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
    此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
    此时链表为1->2<-3<-4<-5
    返回节点5
  • 第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
    此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
    此时链表为1<-2<-3<-4<-5
    返回节点5
  • 出栈完成,最终头节点5->4->3-2->1

77. 组合

1. 题目

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案
组合

2. 代码
class Solution {
public:
    vector<int> temp;
    vector<vector<int>> ans;

    void dfs(int cur, int n, int k) {
        // 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
        if (temp.size() + (n - cur + 1) < k) {
            return;
        }
        // 记录合法的答案
        if (temp.size() == k) {
            ans.push_back(temp);
            return;
        }
        // 考虑选择当前位置
        temp.push_back(cur);
        dfs(cur + 1, n, k);
        temp.pop_back();
        // 考虑不选择当前位置
        dfs(cur + 1, n, k);
    }

    vector<vector<int>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }
};
3. 题解
  1. 需要找到一个长度为 n 的序列 的所有子序列,当前位置是cur,原序列总长度为 n, 原序列的每个位置在答案序列种的状态有被选中和不被选中两种,我们用 temp 数组存放已经被选出的数字;
  2. 在进入 dfs(cur, n, k)之前[1,cur−1] 位置的状态是确定的,而 [cur,n] 内位置的状态是不确定的,dfs(cur,n,k) 需要确定cur 位置的状态,然后求解子问题 dfs(cur+1,n,k)。
  3. 对于cur 位置,我们需要考虑[cur] 取或者不取,如果取,我们需要把 [cur] 放入一个临时的答案数组中temp,再执行dfs(cur+1,n,k),执行结束后需要对temp 进行回溯;
		temp.push_back(cur);
        dfs(cur + 1, n, k);
  1. 如果不取,则直接执行dfs(cur+1,n,k)。在整个递归调用的过程中,cur 是从小到大递增的,当 cur 增加到 n + 1的时候,记录答案并终止递归
 		temp.pop_back();
        // 考虑不选择当前位置
        dfs(cur + 1, n, k);
  1. 如果当前temp 的大小为 s,未确定状态的区间 [cur,n] 的长度为 t,如果 s + t < k,那么即使 t 个都被选中,也不可能构造出一个长度为 k 的序列,故这种情况就没有必要继续向下递归,做一个剪枝,
	if (temp.size() + (n - cur + 1) < k) {
            return;
        }

46. 全排列

1. 题目

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案
全排列

2. 代码
class Solution {
public:
    int n;
    vector <int> res; 
    vector <vector <int>> ans;
    vector <bool> used;
    void dfs (vector<int>& nums, int k) {
        if (k == n) {
            ans.push_back (res);
            return ;
        }
        for (int i = 0;i < n;i++) {
            if (!used[i]) {
                used[i] = true;//为下一次递归做准备
                res[k] = nums[i];
                dfs (nums,k+1);
                used[i] = false;
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        n = nums.size ();
        used.resize (n);
        res.resize (n);
        dfs (nums,0);
        return ans;
    }
};
3. 题解
  1. 数组 nums所有的排列都有一个特点,排列中数据不重复, 且排列中包含数组中所有数据
  2. k代表当前位置, k == n 代表遍历结束, 获得一个排列结果
  3. 循环遍历数据,当前遍历数据若没有被使用,加入res中,并将当前位置标识为被使用过
    递归判断k+1,直到当前数组中数据全被使用,即k==n,此时获取到一个排列结果
  4. 在每次dfs return之后,used[i] = false; 当前数据标识成未被使用,为下一次循环做准备

784. 字母大小写全排列

1. 题目

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合

示例:
输入:S = "a1b2"
输出:["a1b2", "a1B2", "A1b2", "A1B2"]

输入:S = "3z4"
输出:["3z4", "3Z4"]

输入:S = "12345"
输出:["12345"]

字母大小写全排列

2. 代码
class Solution {
public:
        vector<string> ret;
		string str;

		void dfs (const string s, int cur, int n) {
			if (cur == n) {
	            ret.push_back(str); // string遍历完成存入ret
				return;
			}
			// 针对字母类型字符,有两种情况,当前字符和其对应的大/小字母,针对其他字符,只有当前字符一种情况
			if(s[cur] >= 65 && s[cur] <= 90) { // 如果当前是大写字母,存入对应的小写字母
				str[cur] = s[cur] + 32;
				dfs(s, cur+1, n);
			} else if(s[cur] >= 97 && s[cur] <= 122) { // 如果当前是小写字母,存入对应的大写字母
				str[cur] = s[cur] - 32;
				dfs(s, cur+1, n);
			}
			str[cur] = s[cur]; // 存入当前字符
			dfs(s, cur+1, n);
		}
		vector<string> letterCasePermutation(string s) {
			int length = s.length();
			str.resize(length);
            dfs(s,0,length);
            return ret;
		}
};
3. 题解
  1. 针对字母类型字符,有两种情况,当前字符和其对应的大/小字母, 如果是大写则转换为小写, 如果是小写则转换为大写
  2. 针对其他字符,只有当前字符一种情况, 不进行转换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值