LeetCode刷题笔记【中等】

文章目录

  • 两数相加
  • 无重复字符的最长子串
  • 最长回文子串
  • 统计词频
  • 最长递增子序列
  • 前K个高频元素
  • 最小矩形面积
  • 电话号码的字母组合
  • 括号生成
  • 验证二叉搜索树
  • 二叉树的层次遍历
  • 从前序与中序遍历序列构造二叉树
  • 二叉树展开为链表
  • 完全二叉树的节点个数
  • 二叉搜索树中第K小的元素

两数相加

题目描述:

给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

解题思路:
我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐位相加的过程。
LeetCode刷题笔记【中等】_第1张图片


代码实现:

 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *newlist=nullptr;//保存新链表的头
        ListNode *node=nullptr;
        int sum=0;
        int tmp=0;//代表进位
        while(l1!=nullptr||l2!=nullptr||tmp!=0)
        {
            sum=(l1==nullptr?0:l1->val)+(l2==nullptr?0:l2->val)+tmp;
            tmp=sum/10;//tmp保存进位
            if(newlist==nullptr)
            {
               node=new ListNode(sum%10);//代表值
               newlist=node;
            }
            else
            {
                node->next=new ListNode(sum%10);
                node=node->next;
            }
          l1=l1==nullptr?nullptr:l1->next;
          l2=l2==nullptr?nullptr:l2->next;  
        }
        return newlist;
        
    }

无重复字符的最长子串

题目描述:

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。


解题思路:

  • 建立一个256位大小的整型数组来代替哈希表,这样做的原因是ASCII表共能表示256个字符,所以可以记录所有字符
  • 然后我们需要定义两个变量 r e s res res l e f t left left,其中 r e s res res用来记录最长无重复子串的长度, l e f t left left指向该无重复子串左边的起始位置
  • 然后我们遍历整个字符串,对于每一个遍历到的字符,如果哈希表中该字符串对应的值为0,说明没有遇到过该字符,则此时计算最长无重复子串, i − l e f t + 1 i - left +1 ileft+,其中 i i 是最长无重复子串最右边的位置, l e f t left left是最左边的位置
  • 还有一种情况也需要计算最长无重复子串,就是当哈希表中的值小于 l e f t left left,这是由于此时出现过重复的字符, l e f t left left的位置更新了,如果又遇到了新的字符,就要重新计算最长无重复子串。
  • 最后每次都要在哈希表中将当前字符对应的值赋值为 i + 1 i+1 i+1

LeetCode刷题笔记【中等】_第2张图片


代码实现:

  int lengthOfLongestSubstring(string s) {
         set<char> t;//用来保存无重复字符的集合
         int res = 0;// 保存最长无重复子串
         int left = 0;//无重复子串的起始位置
         int right = 0;//用来遍历所有的字符
         while (right < s.size()) {
             if (t.find(s[right]) == t.end()) 
             {
                /*
                * 如果s[right]不在集合中,就表示它还没有出现过
                * 没有出现过的字符插进set集合中
                * 并根据set集合的大小来不断更新res
                */
                 t.insert(s[right++]);
                 res = max(res, (int)t.size());
             }  
             else 
             {
             /*
             * 如果s[right]在集合中存在,就从集合的最左边开始删除,直到
             * 把该重复的字符在集合中删除为止
             */
                 t.erase(s[left++]);
             }
         }
         return res;
     }

对于上述代码的执行过程,可参考下图:
LeetCode刷题笔记【中等】_第3张图片

最长回文子串

题目描述:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

解题思路:

中心扩展法

分两种情况:

  • 奇数位回文串(aba):以 i i i为中心向两边扩展
  • 偶数位回文串(cbbd):以 i , i + 1 i,i+1 i,i+1为中心向两边扩展

代码实现:

string longestPalindrome(string s) {
        int len=s.size();
        if(len==0)
        {
            return "";
        }
        int maxlength=1;
        int start=0;
        // 需要两种情况
        //奇数位回文串(aba),以i为中心向两边扩展
        for(int i=0;i<len;++i)
        {
            int j=i-1;
            int k=i+1;
            while(j>=0&&k<len&&s[j]==s[k])
            {
                if(k-j+1>maxlength)
                {
                    maxlength=k-j+1;
                    start=j;
                }
                
                j--;
                k++;
            }
        }
        //偶数位回文串(cbbd)以i,i+1为中心向两边扩展
        for(int i=0;i<len;++i)
        {
            int j=i;
            int k=i+1;
            while(j>=0&&k<len&&s[j]==s[k])
            {
                if(k-j+1>maxlength)
                {
                    maxlength=k-j+1;
                    start=j;
                }
                
                j--;
                k++;
            }
        }
        if(maxlength>0)
        {
            return s.substr(start,maxlength);
        }
        return "";
    }

统计词频

题目要求:

写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率。

示例:
假设 words.txt 内容如下:

the day is sunny the the
the sunny is is

你的脚本应当输出(以词频降序排列):

the 4
is 3
sunny 2
day 1

脚本实现:

cat words.txt | tr -s ' ' '\n' | sort | uniq -c | sort -r | awk '{print $2" "$1}'

最长递增子序列

题目描述:

给定一个未排序的整数数组,找到最长递增子序列的个数。

举例:

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7][1, 3, 5, 7]

示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5

解题思路:

动态规划

  • d p [ i ] dp[i] dp[i]:表示以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列长度
  • r e s [ i ] res[i] res[i]代表以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的个数
  • 如果以 n u m s [ j ] nums[j] nums[j]结尾的最长递增子序列长度 + 1 > + 1 > +1> n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列长度
  • 更新以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的长度
  • n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的个数就是 d p [ j ] dp[j] dp[j]的个数 r e s [ j ] res[j] res[j]
  • 如果以 n u m s [ j ] nums[j] nums[j]结尾的最长递增子序列长度 + 1 = = 以 n u m s [ i ] + 1 == 以nums[i] +1==nums[i]结尾的最长递增子序列长度
  • n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的个数增加

代码实现:

int findNumberOfLIS(vector<int>& nums) {
     int size = nums.size();
        if(size==0)
        {
            return 0;
        }
    //dp[i]代表到第i个元素为止的最长递增子序列的长度
	vector<int> dp(size);
	//res[i]代表到第i个元素为止的最长递增子序列的个数
	vector<int> res(size);
	int max = 1;//最长递增子序列的长度
	int count = 0;//最长递增子序列的个数
	

	dp[0] = 1;
	res[0] = 1;
	for (int i = 1; i < size; ++i)
	{
		dp[i] = 1;
		res[i] = 1;
		for (int j = 0; j < i; ++j)
		{
			if (nums[i] > nums[j])
			{
			//如果以nums[j]结尾的最长递增子序列长度 + 1 > 以nums[i]结尾的最长递增子序列长度
				if (dp[j] + 1 > dp[i])
				{
					dp[i] = dp[j] + 1;//更新以nums[i]结尾的最长递增子序列的长度
					res[i] = res[j];//nums[i]结尾的最长递增子序列的个数就是dp[j]的个数res[j]
				}
				else if (dp[j] + 1 == dp[i])//如果以nums[j]结尾的最长递增子序列长度 + 1 == 以nums[i]结尾的最长递增子序列长度
				{
					res[i] += res[j];//以nums[i]结尾的最长递增子序列的个数增加
                    
				}
			}
		}
		if (dp[i] > max)// 当前递增子序列的长度大于max,max要进行更新
		{
			max = dp[i];
		}
	}
	for (int i = 0; i < size; ++i)
	{
		if (dp[i] == max)//代表到第i个元素为止的最长递增子序列的长度等于最长递增子序列的长度,取其个数进行相加
		{
			count += res[i];
		}
	}
	return count;
  }

前K个高频元素

题目描述:

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

举例1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

举例2:

输入: nums = [1], k = 1
输出: [1]

解题思路:
使用排序算法对元素按照频率由高到低进行排序,然后再取前 k个元素。


代码实现:

static bool compare(pair<int, int> & p1, pair<int, int> & p2)
{
	return p1.second > p2.second;
}

vector<int> topKFrequent(vector<int>& nums, int k)
{
	vector<pair<int, int>> res;
	vector<int> vec;
	unordered_map<int, int> numMap;
	//vector res;
	for (int i = 0; i < nums.size(); ++i)
	{
		numMap[nums[i]]++;//统计每个数字出现的次数
	}
	auto it = numMap.begin();
	while (it != numMap.end())
	{
		res.push_back(make_pair(it->first, it->second));
		++it;
	}
	sort(res.begin(), res.end(), compare);//根据次数进行排序
	for (int i = 0; i < k; ++i)//取前K个元素
	{
		vec.push_back(res[i].first);
	}
	return vec;
}

最小矩形面积

题目描述:

给定在 xy 平面上的一组点,确定由这些点组成的矩形的最小面积,其中矩形的边平行于 x 轴和 y 轴。
如果没有任何矩形,就返回 0。

示例 1:

输入:[[1,1],[1,3],[3,1],[3,3],[2,2]]
输出:4

示例 2:

输入:[[1,1],[1,3],[3,1],[3,3],[4,1],[4,3]]
输出:2

解题思路:

  • 将每个坐标用一个独一无二的数字表示
  • 首先找对角线上的两个点
  • 之后根据对角线上的点确定其余两个点
  • 最后计算矩形面积

代码实现:

int minAreaRect(vector<vector<int>>& points) {
          set<int> st;
        int minArea = INT_MAX;
        int trans = 40000;
        int len = points.size();
        for(int i=0;i<len;i++){
            int x1 = points[i][0],y1 = points[i][1];
            for(int j=0;j<len;j++){
                int x2 = points[j][0],y2 = points[j][1];
                if(x1 == x2 || y1 == y2) continue;
                if((st.find(x1*trans+y2)!=st.end()) && (st.find(x2*trans+y1)!=st.end())){
                    minArea = min(minArea,abs((x1-x2)*(y1-y2)));
                }
            }
            st.insert(x1*trans+y1);
        }
        return (minArea==INT_MAX)?0:minArea;

   }

电话号码的字母组合

题目描述:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

LeetCode刷题笔记【中等】_第4张图片

示例:

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

解题思路:

  • 首先需要建立一个字典用来存储对应的字符串
  • 根据输入的数字字符串中的每个数字字符找到对应字典序中的字母字符串
  • 通过递归,不断取字母字符串中的字母进行组合

代码实现:

   static void fun(string digits, string nums[],string str, vector<string> &result, int n)
{
	if (n == digits.length())
	{
		result.push_back(str);
	}
	else
	{
		string s = nums[digits[n] - '2'];//德奥数字字符对应的字母字符串
		int length = s.size();
		for (int i = 0; i < length; ++i)
		{
			str += s[i];
			fun(digits, nums, str, result, n + 1);//递归实现字母字符的拼接
			str.pop_back();//删除前一次拼接的结果,进行接下来的拼接
			//例如ad  删除d,进行ae的拼接
		}
	}
}

vector<string> letterCombinations(string digits) 
{
	//首先需要建立一个字典用来存储对应的字符串
	string nums[8] = { "abc", "def", "ghi", "jkl","mno","pqrs","tuv","wxyz" };
    int size=digits.length();
    if(size==0)
    {
        return {};
    }
	vector<string> res;
	string str;
	fun(digits, nums, str, res, 0);
	return res;
}

括号生成

题目描述:

给出 n n n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

举例:
给出 n = 3 n = 3 n=3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

解题思路:

  • l e f t left left代表剩余左括号的个数, r i g h t right right代表剩余右括号的个数
  • 如果 l e f t left left大于 r i g h t right right,代表已产生的左括号的个数少于右括号的个数
    这样就会产生错误
  • 如果 l e f t left left等于 0 0 0&& r i g h t right right等于 0 0 0代表生成一个有效的括号组合

代码实现:

void  fun(int left, int right, vector<string> &res, string str)
    {
        if (left > right)
        {
            return;
        }
        if (left == 0 && right == 0)
        {
            res.push_back(str);
        }
        if (left > 0)
        {
            fun(left - 1, right, res, str + '(');

        }
        if (right > 0)
        {
            fun(left, right - 1, res, str + ')');
        }
    }
    
  vector<string> generateParenthesis(int n) {
    vector<string>  res;
	string str;
	fun(n, n, res, str);
	return res;      
    }

验证二叉搜索树

题目描述:

给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4

代码实现:

bool isValidBST(TreeNode* node,long min,long max)
    {
        if(node==nullptr)
        {
            return true;
        }
        if(node->val<=min||node->val>=max)//小于下确界或者大于上确界都是不正确的
        {
            return false;
        }
		//对于左孩子来说,下确界是最小值,上确界是根节点的值
		//对于右孩子来说,下确界是根节点的值,上确界是最大值
        return  isValidBST(node->left,min,node->val)&&isValidBST(node->right,node->val,max);
        
    }
    bool isValidBST(TreeNode* root) 
    {
        //LONG_MIN表示下确界,LONG_MAX表示上确界
        return isValidBST(root,LONG_MIN,LONG_MAX);
    }

二叉树的层次遍历

题目描述:

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

例如:

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

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

解题思路:

  • 在访问过程中,我们只需要将同一层中的节点同时入队列即可。
  • 在将该queue中所有元素出队列的同时,将下一层的元素进队列,完成交接。

代码实现:

  vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root==NULL){
            return res;
        }
         
        queue<TreeNode*> que;//队列
        que.push(root);
        
        while(!que.empty()){
            int size=que.size();
            vector<int> vec;
            
            while(size--)
            {
                TreeNode* node = que.front();
                vec.push_back(node->val);//取队头元素的值作为最终的结果
                que.pop();
                
                if(node->left){  //将其左节点入队
                    que.push(node->left);
                }
                 
                if(node->right){ //将其右节点入队
                    que.push(node->right);
                }
            }
            res.push_back(vec);//每一层处理完,就保存一次结果
        }
        return res;
    }

从前序与中序遍历序列构造二叉树

题目描述:

根据一棵树的前序遍历与中序遍历构造二叉树。
注意:你可以假设树中没有重复的元素。

举例:

给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

解题思路:

  • 根据前序遍历的结果确定根节点
  • 根据中序遍历的结果确定根节点的左子树和右子树

代码实现:

 TreeNode *buildTree(vector<int> &preorder,vector<int> &inorder,int start1,int end1,int start2,int end2)
    {
        if(start1>end1||start2>end2)
        {
            return nullptr;
        }
        TreeNode *root=new TreeNode(preorder[start1]);
        for(int i=start2;i<=end2;++i)
        {
            if(inorder[i]==preorder[start1])//在中序遍历的结果中找前序遍历的对应根节点
            {
                root->left=buildTree(preorder,inorder,start1+1,
                                     start1+i-start2,start2,i-1);
                
                root->right=buildTree(preorder,inorder,
                                      start1+i-start2+1,end1,i+1,end2);
            }
        }
        return root;
        
       
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int len1=preorder.size();
        int len2=inorder.size();
        if(len1==0)
        {
            return nullptr;
        }
       return buildTree(preorder,inorder,0,len1-1,0,len2-1);  
    }

二叉树展开为链表

题目描述:

给定一个二叉树,原地将它展开为链表。

举例:

例如,给定二叉树

    1
   / \
  2   5
 / \   \
3   4   6
将其展开为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

解题思路:

  • 从根节点开始出发,先检测其左子结点是否存在
  • 如存在则将根节点和其右子节点断开
  • 将左子结点及其后面所有结构一起连到原右子节点的位置
  • 把原右子节点连到原左子结点最后面的右子节点之后

代码实现:

 void flatten(TreeNode* root) 
    {
        if(root==nullptr)
        {
            return ;
        }
        TreeNode *cur=root;
        while(cur)
        {
            if(cur->left)
            {
                TreeNode *tmp=cur->left;
                while(tmp->right)
                {
                    tmp=tmp->right;
                }
                tmp->right=cur->right;//将左子结点及其后面所有结构一起连到原右子节点的位置
                cur->right=cur->left;//把原右子节点连到原左子结点最后面的右子节点之后
                cur->left=nullptr;
            }
            cur=cur->right;
        }    
    }

完全二叉树的节点个数

问题描述:

给出一个完全二叉树,求出该树的节点个数。
说明:
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h h h层,则该层包含 1~ 2 h 2^h 2h 个节点。

举例:

输入: 
    1
   / \
  2   3
 / \  /
4  5 6

输出: 6

解题思路:
结点个数等于其左孩子的个数加上其右孩子的个数+1


代码实现:

int countNodes(TreeNode* root) {
        if(root==nullptr)
        {
            return 0;
        }
        //结点个数等于其左孩子的个数加上其右孩子的个数+1
       return countNodes(root->left)+countNodes(root->right)+1;   
    }

二叉搜索树中第K小的元素

题目描述:

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3

解题思路:

中序遍历的第k个节点就是第K小的元素


非递归代码实现:

int kthSmallest(TreeNode* root, int k) {
        int cnt = 0;
        stack<TreeNode*> s;
        TreeNode *p = root;
        while (p || !s.empty()) {
            while (p) { //让其左孩子一直入栈
                s.push(p);
                p = p->left;
            }
            p = s.top(); s.pop();//此时的栈定元素就是我们需要访问的元素
            ++cnt;
            if (cnt == k) return p->val;
            p = p->right;//让栈定元素的右孩子入栈
        }
        return 0;
    }

递归实现:

TreeNode * smallK(TreeNode *node,int k,int & i)
    {
       if(node==nullptr)
       {
           return nullptr;
       }
       TreeNode *p1= smallK(node->left,k,i);
       if(p1!=nullptr)
       {
           return p1;
       }
        i++;
        if(i==k)
        {
            return node ;
        }
      return  smallK(node->right,k,i);
    }

你可能感兴趣的:(LeetCode刷题)