LeetCode初级算法题回顾(三)链表&树

本文回顾了LeetCode中的链表和树基础算法题,包括链表的删除节点、翻转、合并等操作,以及二叉树的最大深度、验证二叉搜索树、对称性检查等。介绍了快慢指针、递归等解题技巧,并推荐了相关题目供读者练习。
摘要由CSDN通过智能技术生成

本回顾是对LeetCode 探索栏目中的“初级算法题”进行总结,归纳该栏目中做题的心得和体会。(注:不是leetcode中的所有简单难度的算法题)

该探索项目分为九个子栏目:数组、字符串、链表、树、排序和搜索、动态规划、设计问题、数学及其他。

关于链表的基础知识:LeetCode链表&链表概念梳理及代码示例

目录

A.链表 (主要考察指针) 链表初级算法题目一览

1)删除链表中的节点

删除链表的倒数第N个节点

反转链表

合并两个有序链表

回文链表

环形链表

B.树(指针、递归、动态规划)

1)二叉树的最大深度(递归)

2)验证二叉搜索树(二叉搜索树的特性、递归)

3)对称二叉树

4)二叉树的层次遍历

5)将有序数组转换为二叉搜索树



A.链表 (主要考察指针) 链表初级算法题目一览

链表问题相对容易掌握。 不要忘记"双指针解法",它不仅适用于数组问题,而且还适用于链表问题。(回文链表、环形链表

另一种大大简化链接列表问题的方法是"Dummy node" 节点技巧,所谓Dummy Node其实就是带头节点的指针。

我们推荐以下题目:反转链表,合并两个有序链表和环形链表。

更有额外的挑战,你可以尝试运用递归来解决这些问题:反转链表,回文链表和合并两个有序链表。


1)删除链表中的节点

首先我们要知道链表基本操作:怎么删除链表的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

现有一个链表 -- 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;
    }
};

-----------------------------

给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。

示例:给定一个链表: 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)


B.树(指针、递归、动态规划)

树比链表稍微复杂,因为链表是线性数据结构,而树不是。 树的问题可以由广度优先搜索深度优先搜索解决。 在本章节中,我们提供了一个对于练习广度优先遍历很好的题目。

二叉树是最常见的树的类型的面试题,你需要掌握关于 二叉树的 前序遍历(根→左节点→右节点)、中序遍历(左节点→根→右节点)及后序遍历的(左节点→右节点→根)

最核心思路在于  每一个子树 也是一个树 的递归思想

题目一览 → 二叉树的初级算法题


1)二叉树的最大深度(递归)

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:给定二叉树 [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;

  1. 注意子树计算深度的终止条件 if (!root->right && !root->left) return 1;
  2. 注意特殊情况:输入为空的讨论 if (!root) return 0;

最简洁:

  int maxDepth(TreeNode* root) {
       return root ==NULL ? 0 : max(maxDepth(root->left), maxDepth(root->right)) + 1;
        }
    

-----------------

2)验证二叉搜索树(二叉搜索树的特性、递归)

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 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整型

------------------

3)对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [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 (根节点的左子树的左节点 == 根节点的右子树的右节点 && 根节点的左子树的右节点 == 根节点的右子树的左节点)

递归~

-------------------

4)二叉树的层次遍历

给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。

例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> allElem;
        if (root !=NULL) getLevelElem( root, 0, allElem);
        return allElem;
    } 
    
    void getLevelElem(TreeNode* root, int level, vector<vector<int>>& allElem) {
        //先占坑
        if ( allElem.size() < level+1 ){
            vector<int> 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. 利用 if ( allElem.size() < level+1 ){  vector<int> levelElem;  allElem.push_back( levelElem );  }得到每一层的数组; 
  2. 赋值时注意,直接赋值到二维数组中: allElem[level].push_back(root->val); 

-----------------------

5)将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5
  1. 如果将二叉搜索树按中序遍历的话,得到的就是一个有序数组了。
  2. 那么反过来,我们可以得知,根节点应该是有序数组的中间点,从中间点分开为左右两个有序数组,在分别找出其中间点作为原中间点的左右两个子节点,是二分查找法的核心思想。
  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:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        int n = nums.size();
        TreeNode* root = Node(nums, 0, n-1);  //注意n-1 

        return root;
    }
    
    TreeNode* Node(vector<int>& nums, int begin, int end) {
        if (end<begin) return NULL; //注意不包括=
        
        TreeNode* root = new TreeNode(nums[(begin+end)/2]);
        
        root->left = Node(nums, begin, (begin+end)/2-1);  
        root->right = Node(nums, (begin+end)/2+1, end); 
        
        return root;   
    }
};

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值