LeetCode 初级 链表部分
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
- 示例:
- 给定一个链表: 1->2->3->4->5, 和 n = 2.
- 当删除了倒数第二个节点后,链表变为 1->2->3->5.
- 说明:
- 给定的 n 保证是有效的。
- 进阶:
- 你能尝试使用一趟扫描实现吗?
解答:
- 思路:
- 方法: 快慢指针法
- 快指针指向n个之后的元素,慢指针维持和快指针的间隔为n个元素
- 边界条件:
- 初始化的时候快指针就到nullptr了,此时 输入n等于链表的长度,相当于删除链表的第一个节点1
- 循环的时候,快指针的next为nullptr,此时快指针指向末尾,慢指针指向 待删除节点的上一个节点。
- 注意删除倒数第n个节点,和最后节点应该相差n-1个节点的,这里维持的间隔为n个节点,所以慢指针会指向待删除上上一个节点
- 方法: 快慢指针法
- Code
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode* first=head; while(n--!=0) {first=first->next;} if(!first) {return head->next;} // 表示删除第一个 ListNode* sec=head; while(first->next!=NULL){ sec=sec->next; first=first->next; } sec->next=sec->next->next; return head; } };
反转链表
- 反转一个单链表。
- 示例:
- 输入: 1->2->3->4->5->NULL
- 输出: 5->4->3->2->1->NULL
- 进阶:
- 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
解答
- 思路1: 递归法
- 意思阐述:
- 原来: 1->2->3->NULL
- 现有: 1->NULL->3->2; 1->2
- 修改为: 1->next->next=1; 1->next=nullptr
- 3->2->1->Null
- 边界条件:
- 推出条件: head == null || head->next==null
- Code
public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode nextTemp = curr.next; curr.next = prev; prev = curr; curr = nextTemp; } return prev; }
- 意思阐述:
- 思路2: 迭代法
- 意思阐述:
- 新建一个prev==null
- 然后从head到尾进行迭代,将prev插入到迭代值的next上
- 边界条件:
- 当cur==null的时候,此时迭代可以终止了
- 不是cur->next==null,因为此时cur的val是有效的
- 返回prev
- 当cur==null的时候,此时迭代可以终止了
- Code
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* cur = head; while(cur!=nullptr){ ListNode* nextTemp = cur->next; cur->next = prev; prev = cur; cur = nextTemp; } return prev; } };
- 意思阐述:
回文链表
- 请判断一个链表是否为回文链表。
- 示例
- 示例 1:
- 输入: 1->2
- 输出: false
- 示例 2:
- 输入: 1->2->2->1
- 输出: true
- 示例 1:
- 进阶:
- 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解答
- 思路:
- 找到中点
- 后半段逆序
- 进行比较
- 注意点:
- 找中点的时候使用快慢指针,快指针走两步,慢指针走一步,然后随时判断快指针是否为nullptr
- 逆序时 不停的把prev放在末尾即可
验证二叉搜索树
- 给定一个二叉树,判断其是否是一个有效的二叉搜索树。
- 假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
- 示例:
- 示例 1:
输入: 2 / \ 1 3 输出: true
- 示例 2:
输入: 5 / \ 1 4 / \ 3 6 输出: false
- 示例 1:
- 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
解答:
- 方法一 递归
- 思路
- 每次判断一个
- 注意上下边界问题:
- 左: low - root.left
- 右: root.right - high
- Code
class Solution { public: bool validbst(TreeNode* root, long l, long r){ if(!root) {return true;} if(root->val <= l) {return false;} if(root->val >= r) {return false;} bool left = validbst(root->left, l, root->val); bool right = validbst(root->right, root->val, r); bool cur=true; if(root->left){ cur &= root->val > root->left->val; } if(root->right){ cur &= root->val < root->right->val; } return left&& right && cur; } bool isValidBST(TreeNode* root) { return validbst(root, LONG_MIN, LONG_MAX); } };
- 思路
- 方法二: 迭代(DFS,深度优先搜索算法)
- 思路:
- 和递归法类似,为此上下限
- 使用堆栈来维持边界
- Code
class Solution { public: stack<pair<TreeNode*, pair<long, long>>> s; void update(TreeNode* root, long low, long high){ s.push({root, {low, high}}); } bool validbst(TreeNode* root, long l, long r){ update(root, l, r); while(not s.empty()){ TreeNode* t = s.top().first; long low = s.top().second.first; long high = s.top().second.second; s.pop(); if(t==nullptr){continue;} if(t->val <= low) {return false;} if(t->val >= high) {return false;} update(t->right, t->val, high); update(t->left, low, t->val); } return true; } bool isValidBST(TreeNode* root) { return validbst(root, LONG_MIN, LONG_MAX); } };
- 思路:
- 方法三: 中序遍历
-
思路
- Code
bool isValidBST(TreeNode* root) { // return validbst(root, LONG_MIN, LONG_MAX); stack<TreeNode*> s; long order = LONG_MIN; while(true){ if(s.empty() && root==nullptr){break;} while(root){ s.push(root); root=root->left; } root = s.top(); s.pop(); if(root->val <= order) {return false;} order = root->val; root = root->right; } return true; }
对称二叉树
- 给定一个二叉树,检查它是否是镜像对称的。
- 例
- 例1,二叉树 [1,2,2,3,4,4,3] 是对称的。
1 / \ 2 2 / \ / \ 3 4 4 3
- 例2, [1,2,2,null,3,null,3] 则不是镜像对称的:
1 / \ 2 2 \ \ 3 3
- 例1,二叉树 [1,2,2,3,4,4,3] 是对称的。
- 说明: 如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
解答
- 方法一 递归
- 思路:
- 递归输入:
left
和right
- 递归判断条件:
left==nullptr && right==nullptr
left和right都是null,为trueleft==nullptr || right==nullptr
left和right其中有一个为null,为falseleft->val != right->val
- 循环递归:
- 内容:
- {
left->left
,right->right
} - {
left->right
,right->left
}
- {
- 关系:
&&
- 内容:
- 递归输入:
- Code:
class Solution { public: bool Symmetric(TreeNode* left, TreeNode* right){ // 函数主题 if(left==nullptr && right==nullptr){return true;} if(left==nullptr && right!=nullptr){return false;} if(right==nullptr && left!=nullptr){return false;} if(left->val != right->val){return false;} return Symmetric(left->left, right->right) && Symmetric(left->right, right->left); } bool isSymmetric(TreeNode* root) { // 递归 if(root==nullptr) {return true;} return Symmetric(root->left, root->right); } };
- 思路:
- 方法二: 迭代法
- 思路:
- 输出储存形式:
stack<pair<TreeNode*, TreeNode*>>
- 迭代体输入:{
left
,right
} - 迭代判断条件:
left==nullptr && right==nullptr
left和right都是null,为trueleft==nullptr || right==nullptr
left和right其中有一个为null,为falseleft->val != right->val
- 迭代提输出:
- {
left->left
,right->right
} - {
left->right
,right->left
}
- {
- 输出储存形式:
- Code
bool isSymmetric(TreeNode* root) { // 递归 //if(root==nullptr) {return true;} //return Symmetric(root->left, root->right); // 迭代法 if(root==nullptr) {return true;} stack<pair<TreeNode*, TreeNode*>> s; s.push({root->right, root->left}); while(not s.empty()){ TreeNode* r = s.top().first; TreeNode* l = s.top().second; s.pop(); if(l==nullptr && r==nullptr){continue;} if(l==nullptr || r==nullptr){return false;} if(l->val != r->val){return false;} s.push({r->right, l->left}); s.push({r->left, l->right}); } return true; }
- 思路:
二叉树的层次遍历
- 给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
- 例如:
- 给定二叉树: [3,9,20,null,null,15,7],
3 / \ 9 20 / \ 15 7
- 返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
- 给定二叉树: [3,9,20,null,null,15,7],
解答
- 方法一: 递归
- 思路:
- 递归输入:
queue<TreeNode*> s; // 保存了某一个层次的节点
- 递归输出:
- 定义在类中方便访问:
vector<vector<int>> result;
- 本函数无返回值
- 定义在类中方便访问:
- 递归主体:
- 输入:
s
- 输出:
queue<TreeNode*> z; // 循环递归的内容
vector<int> result_cur; // 当前递归主体的结果
- 逻辑
s 中的每以项为 m
m==nullptr; continue
m->left 和 m->right 添加至z
m->val 添加至于result_cur
- 输入:
- 循环递归:
- 递归条件:
not z.empty()
- 内容:
递归主体中的z
- 返回: 无
- 递归条件:
- 结果返回:
- 递归输入:
- Code
class Solution { public: vector<vector<int>> result; void reclevelOrder(queue<TreeNode*> z) { result.push_back(vector<int>()); queue<TreeNode*> k; while(not z.empty()){ TreeNode* m = z.front(); z.pop(); if(m==nullptr){continue;} k.push(m->left); k.push(m->right); result.back().push_back(m->val); } if(not k.empty()){ reclevelOrder(k); } if(result.back().empty()){ result.pop_back(); } } vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> k; k.push(root); reclevelOrder(k); return result; } };
- 思路:
- 方法二: 迭代
- 思路
- 输出储存形式:
queue<TreeNode*> s; // 某一个level的点
vector<int> levelResult; // 当前等级的
- 迭代体输入:
s
- 迭代判断条件:
s中的每一个为m
m!=nullptr;
m->left 和 m->right 添加至s中
m->value添加至levelResult中
- 迭代提输出:
s
- 输出储存形式:
- Code
vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> k; vector<vector<int>> result; k.push(root); while(not k.empty()){ int qs = k.size(); vector<int> levelresult; while(qs--){ TreeNode* t = k.front(); k.pop(); if(t==nullptr){continue;} levelresult.push_back(t->val); k.push(t->left); k.push(t->right); } if(!levelresult.empty()){ result.push_back(levelresult); } } return result; }
- 思路
将有序数组转换为二叉搜索树
- 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
- 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
- 示例:
- 给定有序数组: [-10,-3,0,5,9],
- 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0 / \ -3 9 / / -10 5
解
- 方法一: 递归
- 思路: 将列表从中间分开,不停的二分下去即可得到解
- 迭代主体:
- 输入:
- 列表:
vector<int>& nums
- 下限ID:
int left
- 上限ID:
int right
- 列表:
- 输出:
- curleft = 迭代(nums, mid, left)
- curright = 迭代(nums, right, mid)
- 逻辑:
- 最底层条件:
if(left>right) return nullptr
mid = ceil((left+right)/2)
- 最底层条件:
- 输入:
- 迭代主体:
- Code
TreeNode* sortedArrayToBST(vector<int>& nums) { return helper(nums, 0, nums.size()-1); } TreeNode* helper(vector<int>& nums, int l, int r) { if(l>r){return nullptr;} int mid = floor((l+r)/2); auto t = new TreeNode(nums[mid]); t->left = helper(nums, l, mid-1); t->right = helper(nums, mid+1, r); return t; }
- 思路: 将列表从中间分开,不停的二分下去即可得到解
- 方法二: 迭代法
- 思路
- 迭代函数输入:
vector<int> nums;
pair<int, int> match; // 上下限
- 迭代主体:
- 迭代储存形式:
stack<pair<TreeNode**, pair<int, int>>> s;
s.first
表示当前迭代level应该储存的位置,这里是二级指针s.second
表示当前迭代 的上下限
- 迭代逻辑:
- 取输入:
TreeNode* &LevelSave = *(s.top.first);
pair<int, int> LevelMatch= s.top.second;
- 控制条件:
if(LevelMatch->first > LevelMatch->second) continue;
int mid = floor((LevelMatch.first+LevelMatch.second)/2);
- 喂输出:
LevelSave=new TreeNode(nums[mid]);
s.push({&(LevelSave.left), {LevelMatch.first, mid-1}});
s.push({&(LevelSave.right), {mid+1, LevelMatch.second}});
- 取输入:
- 迭代储存形式:
- 迭代函数输入:
- Code
TreeNode* iterhelper(vector<int>& nums, pair<int, int> match){ TreeNode* result = nullptr; stack<pair<TreeNode**, pair<int, int>>> s; s.push({&result, match}); while(not s.empty()){ pair<TreeNode**, pair<int, int>> m = s.top(); s.pop(); TreeNode* &curnode = *(m.first); pair<int, int> curmatch = m.second; if(curmatch.first > curmatch.second){continue;} int mid = floor((curmatch.first + curmatch.second)/2); curnode = new TreeNode(nums[mid]); s.push({&(curnode->left), {curmatch.first, mid-1}}); s.push({&(curnode->right), {mid+1, curmatch.second}}); } return result; }
- 思路
while(n–):先判断n是不是0。如果n不为0,n减1,执行循环体。n的变化过程:n-1 ~ 0; while(–n):n先减1,再判断n是不是0。如果n不为0,执行循环体。n的变化过程:n-1 ~ 1 ↩︎