本回顾是对LeetCode 探索栏目中的“初级算法题”进行总结,归纳该栏目中做题的心得和体会。(注:不是leetcode中的所有简单难度的算法题)
该探索项目分为九个子栏目:数组、字符串、链表、树、排序和搜索、动态规划、设计问题、数学及其他。
关于链表的基础知识:LeetCode链表&链表概念梳理及代码示例
目录
A.链表 (主要考察指针) 链表初级算法题目一览
1)删除链表中的节点
删除链表的倒数第N个节点
反转链表
合并两个有序链表
回文链表
环形链表
B.树(指针、递归、动态规划)
1)二叉树的最大深度(递归)
2)验证二叉搜索树(二叉搜索树的特性、递归)
3)对称二叉树
4)二叉树的层次遍历
5)将有序数组转换为二叉搜索树
链表问题相对容易掌握。 不要忘记
"双指针解法"
,它不仅适用于数组问题,而且还适用于链表问题。(回文链表、环形链表)另一种大大简化链接列表问题的方法是
"Dummy node" 节点技巧
,所谓Dummy Node其实就是带头节点的指针。我们推荐以下题目:反转链表,合并两个有序链表和环形链表。
更有额外的挑战,你可以尝试运用
递归
来解决这些问题:反转链表,回文链表和合并两个有序链表。
首先我们要知道链表基本操作:怎么删除链表的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
示例 1: 输入: head = [4,5,1,9], node = 5 输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
说明:
- 链表至少包含两个节点。 链表中所有节点的值都是唯一的。
- 给定的节点为非末尾节点并且一定是链表中的一个有效节点。 不要从你的函数中返回任何结果。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
-----------------------------
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:给定的 n 保证是有效的。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head) return NULL;
ListNode* first = head;
ListNode* second = head;
while(n!=0 && first->next!= NULL) {
first = first -> next;
n--;
}
if(n>1) return head;
if(n==1) return head->next;
while( first->next){
second = second->next;
first = first->next;
}
second->next=second->next->next;
return head;
}
};
在不能直接获取第n个元素的链表里,通过使用两个指针,我们可以得到我们想要的位置的指针。
注意指针越界的特殊情况讨论 (head是否为空? 删除的是头节点怎么办? if(!head) return NULL; if(n==1) return head->next;)
----------------------------------------
反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
进阶: 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = NULL;
while(cur){
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
-------------------------
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例: 输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
//应用递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1) return l2;
if(!l2) return l1;
if( l1->val < l2->val){
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else{
l2->next = mergeTwoLists(l2->next, l1);
return l2;
}
}
};
思路:应用递归。较小值的指针指向递归后的结果。
--------------------------
请判断一个链表是否为回文链表。
示例 1: 输入: 1->2 输出: false
示例 2: 输入: 1->2->2->1 输出: true
//快慢指针法找到中点,中点后反转链表
//一,find mid node 使用快慢指针(一个每次走一步一个每次走两步)找到链表中点。 二,reverse 逆序后半部分。 三,check 从头、中点,开始比较是否相同。
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
ListNode* prev = nullptr;
//find mid one
while(fast){
slow = slow->next;
fast = fast->next? fast->next->next: fast->next; //fast->next 为空的时候就没有fast->next->next了。
}
//reverse.
//最终得到的prev指向原顺序的最后一个节点。反转后的后半段最后一个节点为空,即整段的中间+2节点为空
while(slow){
ListNode* temp = slow->next;
slow->next = prev;
prev = slow;
slow = temp;
}
//compare 不用判断奇偶
while ( head && prev)
{
if (head->val!=prev->val)
return false;
else{
head = head->next;
prev = prev->next;
}
}
return true;
}
};
注意快慢指针法中快指针 fast->next是否会指向空,若是,fast->next->next不存在
//fast->next 为空的时候没有fast->next->next
fast = fast->next? fast->next->next: fast->next;
------------------------
//双指针————快慢指针法,如果存在环,不同速度的指针,终会相遇
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head->next) return false;
ListNode* fast = head;
ListNode* slow = head;
while (fast->next && fast->next->next) {
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
return true;
}
}
return false;
}
};
快慢指针法。依旧要注意快慢指针法中快指针 fast->next->next 是否存在 while (fast->next && fast->next->next)
树比链表稍微复杂,因为链表是线性数据结构,而树不是。 树的问题可以由
广度优先搜索
或深度优先搜索
解决。 在本章节中,我们提供了一个对于练习广度优先遍历
很好的题目。二叉树是最常见的树的类型的面试题,你需要掌握关于 二叉树的 前序遍历(根→左节点→右节点)、中序遍历(左节点→根→右节点)及后序遍历的(左节点→右节点→根)
最核心思路在于 每一个子树 也是一个树 的递归思想
题目一览 → 二叉树的初级算法题
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:给定二叉树
[3,9,20,null,null,15,7]
,3 / \ 9 20 / \ 15 7
返回它的最大深度 3 。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
思路 树的最大深度 = max(左子树深度,右子树深度)+1;
最简洁:
int maxDepth(TreeNode* root) {
return root ==NULL ? 0 : max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
-----------------
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入: 2 / \ 1 3 输出: true
示例 2:
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode* root) {
if (!root) return true;
//注意一个节点可能是左子树上的右子树的节点,或者是右子树上的左子树的节点。所以有一个max和min的范围
return ( isValidNode(root->left,root->val,LONG_MIN) && isValidNode(root->right, LONG_MAX, root->val));
}
bool isValidNode(TreeNode* root, long maxval, long minval) {
if (!root) return true;
if (root->val >= maxval || root->val <= minval) return false;
return ( isValidNode(root->left,root->val,minval) && isValidNode(root->right,maxval,root->val));
}
};
首先,对输入为空的讨论
其次,注意这个节点可能是左子树上的右子树的节点,或者是右子树上的左子树的节点。所以每一个节点都有一个max和min的范围
最后 递归 *题目还有一个坑是,注意int的溢出,设置为long整型
------------------
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树
[1,2,2,3,4,4,3]
是对称的。1 / \ 2 2 / \ / \ 3 4 4 3
但是下面这个
[1,2,2,null,3,null,3]
则不是镜像对称的:1 / \ 2 2 \ \ 3 3
说明:如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return isSameVal( root->left,root->right);
}
bool isSameVal( TreeNode* rootl, TreeNode* rootr){
if (rootl==NULL && rootr == NULL) return true;
if (rootl==NULL || rootr == NULL || rootl->val != rootr->val) return false;
return isSameVal( rootl->left, rootr->right ) && isSameVal( rootl->right, rootr->left);
}
};
只需关注:
return (根节点的左子树的左节点 == 根节点的右子树的右节点 && 根节点的左子树的右节点 == 根节点的右子树的左节点)
递归~
-------------------
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树:[3,9,20,null,null,15,7]
,3 / \ 9 20 / \ 15 7
返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
class Solution {
public:
vector> levelOrder(TreeNode* root) {
vector> allElem;
if (root !=NULL) getLevelElem( root, 0, allElem);
return allElem;
}
void getLevelElem(TreeNode* root, int level, vector>& allElem) {
//先占坑
if ( allElem.size() < level+1 ){
vector levelElem;
allElem.push_back( levelElem );
}
//添加元素
allElem[level].push_back(root->val);
//递归
if( root->left != NULL) getLevelElem(root->left, level+1, allElem);
if( root->right!= NULL) getLevelElem(root->right, level+1, allElem);
}
};
思路:还是递归,只关注每一个节点
-----------------------
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9], 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \ -3 9 / / -10 5
- 如果将二叉搜索树按中序遍历的话,得到的就是一个有序数组了。
- 那么反过来,我们可以得知,根节点应该是有序数组的中间点,从中间点分开为左右两个有序数组,在分别找出其中间点作为原中间点的左右两个子节点,是二分查找法的核心思想。
- 所以这道题考的就是二分查找法。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* sortedArrayToBST(vector& nums) {
int n = nums.size();
TreeNode* root = Node(nums, 0, n-1); //注意n-1
return root;
}
TreeNode* Node(vector& nums, int begin, int end) {
if (endleft = Node(nums, begin, (begin+end)/2-1);
root->right = Node(nums, (begin+end)/2+1, end);
return root;
}
};