LeetCode中级算法题目总结(2)

欢迎来我的博客 http://www.blackblog.tech,我的简书 https://www.jianshu.com/u/55a1bc4688c6

这是一篇笔记型Blog,主要存一下最近练的代码的笔记。LeetCode的代码,在云端,复习起来麻烦,就这样存下来。
目前的练习为LeetCode中级算法与每日模拟赛.
没事刷一刷LeetCode还是可以提高一下基本的代码能力的。

我们接上一章继续

LeetCode172 阶乘后的零

题目

给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:

输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。

示例 2:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

说明: 你算法的时间复杂度应为 O(log n) 。

C++代码

class Solution {
public:
    int trailingZeroes(int n) {
        int count=0;
        while(n)
        {
            count+=n/5;
            n/=5;
        }
        return count;
    }
};

体会

技巧题!
关键在于5的数量了那么该问题的实质是要求出1~100含有多少个5由特殊推广到一般的论证过程可得:
1、 每隔5个,会产生一个0,比如 5, 10 ,15,20…
2 、每隔 5×5 个会多产生出一个0,比如 25,50,75,100
3 、每隔 5×5×5 会多出一个0,比如125.

所以100!末尾有多少个零为:100/5+100/25=20+4=24那么1000!末尾有多少个零呢?同理得: 1000/5+1000/25+1000/125=200+40+8=248

接着,请问N!的末尾有多少个零呢?
其实 也是同理的
N/5+N/25+……
如计算 2009! 的末尾有多少个0:2009/5 = 401
1~2009之间有 401 个数是 5 的倍数(余数省略).401/5 = 80
1~2009 之间有 80 个数是 25 的倍数.80/5 = 16
1~2009 之间有 16 个数是 125 的倍数. 16/5 = 3
1~2009 之间有 3个数是 625 的倍数. 3/5 = 0
1~2009 之间有 0 个数是 3125 的倍数.
所以, 2009! 的末尾有 401 + 80 + 16 + 3 = 500 个0.

LeetCode50 Pow(x, n)

题目

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

输入: 2.00000, -2
输出: 0.25000

解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。

C++代码

class Solution {
public:
    double myPow(double x, int n) {
        if(n<0) return 1/power(x,-n);
        else return power(x,n);
    }
    double power(double x,int n)
    {
        if(n==0) return 1;
        double half = power(x,n/2);
        if(n%2==0) return half*half;
        else return x*half*half;
    }
};

体会

技巧题!
这里解释一下代码
比如我们计算2^7
可以拆分为 2^3 * 2^3 * 2
2^3可以继续拆分为 2^1 * 2^1 * 2
所以2^7可以拆分了 2^3 * 2^3 * 2 分为 2^1 * 2^1 * 2 * 2^1 * 2^1 * 2 * 2

LeetCode105 中序遍历二叉树

题目

给定一个二叉树,返回它的中序 遍历。
示例:

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,2]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

C++代码

递归(不考虑安全性)

class Solution {
public:
    vector<int> res;
    void inorderTraversal2(TreeNode* root)
    {
        if(root==NULL) { return;}
        inorderTraversal(root->left);//遍历左子节点
        res.push_back(root->val);//存入当前节点的值 也就是中节点
        inorderTraversal(root->right);//遍历右子节点
        return ;
    }
    vector<int> inorderTraversal(TreeNode* root) {
        inorderTraversal2(root);
        return res;
    }
};

递归(考虑安全性,不使用全局变量)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if(root==NULL) { vector<int> v;return v;}
        vector<int> res;
        vector<int> tmp_left;
        tmp_left = inorderTraversal(root->left);//遍历左子节点
        if(!tmp_left.empty())
        {
            for(int i=0;i<tmp_left.size();i++)
            {
                res.push_back(tmp_left[i]);//上一次递归的结果存储在tmp中,用一个循环获取所有数据。
            }
        }
        res.push_back(root->val);//存入当前节点的值 也就是中节点

        vector<int> tmp_right;
        tmp_right = inorderTraversal(root->right);//遍历右子节点
        if(!tmp_right.empty())
        {
            for(int i=0;i<tmp_right.size();i++)
            {
                res.push_back(tmp_right[i]);
            }
        }
        return res;
    }
};

迭代法(使用栈完成)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode* current = root;
        while(current||!s.empty())
        {
            if(current)
            {
                s.push(current);//当前节点压栈
                current = current->left;//先走左边
            }
            else
            {
                res.push_back(s.top()->val);//将栈顶元素存入向量,现在curr的父节点,此时curr==null
                current = s.top()->right;//修改current为当前栈顶元素的右节点,
                s.pop();//弹出当前栈顶元素
            }
        }
        return res;
    }
};

体会

难度不高
中序遍历,如果不考虑安全性的话,几句话就可以写完呢。
使用栈的方法可以大大提升效率,建议使用。

LeetCode55 跳跃游戏

题目

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 从位置 011 步, 然后跳 3 步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

C++代码

class Solution {
public:
    bool canJump(vector<int>& nums) {
        bool dp[nums.size()*2]={false};
        dp[0]=true;
        for(int i=0;i<nums.size();i++) //遍历每一个位置
        {
            for(int j=1;j<=nums[i];j++)//计算当前位置每种可能跳的情况
            {
                if(dp[i]) dp[i+j]=true;//如果当前位置能跳到,下一个位置才能
            }
        }
        return dp[nums.size()-1];
    }
};

体会

emmmmm,很迷,这个也算是DP???
不过细想,也算是划分子问题了。
很简单
二重循环,都遍历一遍,然后计算每一个可以到达的路径。重点在于if(dp[i]) dp[i+j]=true,只有当前位置能够到达,当前位置的下一个位置才能到。

LeetCode62 不同路径

题目

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
说明:m 和 n 的值均不超过 100。

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 2:

输入: m = 7, n = 3
输出: 28

C++ 代码

class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[200][200]={0};
        dp[1][1]=0;
        for(int i=1;i<=m;i++)
            dp[i][1]=1;
        for(int i=1;i<=n;i++)
            dp[1][i]=1;
        for(int i=2;i<=m;i++)
        {
            for(int j=2;j<=n;j++)
            {
                dp[i][j]=std::max(dp[i][j],dp[i][j-1]+dp[i-1][j]);
            }
        }
        return dp[m][n];
    }
};

体会

基本DP题
dp[i][j]表示网格为i*j时候的方法数
状态转移方程:dp[i][j] = dp[i][j-1]+dp[i-1][j]
注意初始化的时候要将第一行第一列初始化为1,因为第一行第一列怎么走都是1。

LeetCode322 零钱兑换

题目

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

说明:
你可以认为每种硬币的数量是无限的。

C++代码

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int min=99999999999;
        int dp[100000];

        //过滤一下特别大的数字
        for(int i=0;i<coins.size();i++)
            if(coins[i]>amount) coins[i]=0;
        //初始化DP数组
        for(int i=1;i<=amount;i++)
            dp[i]=999999999;
        //当j==coins[i]时,coins[i]所对应的价值至少有一个硬币可以构成
        for(int i=0;i<coins.size();i++)
            if(coins[i]!=0) dp[coins[i]]=1;

        for(int i=0;i<coins.size();i++)
        {
            for(int j=coins[i];j<=amount;j++)
            {
                if(coins[i]!=0) 
                    dp[j]=std::min(dp[j],dp[j-coins[i]]+1);
            }
        }
        if(dp[amount] == 999999999) return -1;
        else return dp[amount];
    }
};

体会

完全背包+最小值+恰好装满
dp[j]表示:前i个硬币,价值为j时候的硬币数量
使用一维数组压缩空间
状态转移方程: dp[j]=min(dp[j],dp[j-coins[i]]+1)
完全背包:正序循环
最小值:min
恰好装满:初始化为inf

LeetCode300 Longest Increasing Subsequence

题目

给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

C++代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()==0) return 0;
        int dp[10000];//每一个上升子序列的长度至少是1
        int res=1;
        for(int i=0;i<nums.size();i++)
            dp[i] = 1;
        for(int i=0;i<nums.size();i++) //遍历所有的长度
        {
            for(int j=0;j<i;j++)//模拟添加逐个数字
            {
                if(nums[j]<nums[i])//如果nums[j]<num[i]保证是递增序列,
                {
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
            res = max(res,dp[i]);
        }
        return res;
    }
};

体会

首先区分两个题目!
LIS:最长递增子序列(子串)
LCS:最长公共子序列(子串)

最长公共子串(Longest Common Substring)与最长公共子序列(Longest Common Subsequence)的区别:
子串要求在原字符串中是连续的,而子序列则只需保持相对顺序一致,并不要求连续。
例如X = {a, Q, 1, 1}; Y = {a, 1, 1, d, f}那么,{a, 1, 1}是X和Y的最长公共子序列,但不是它们的最长公共字串。
对该题是同理

这个题是求解最长上升子序列
采用动态规划的思想
dp[i] 表示 长度为i的数列中最长递增子序列的长度!注意是长度!
我们划分为子问题
从i开始遍历长度,每次一个新长度就模拟添加数字,从第一个开始添加,如果新添加的一个数字小于当前nums[i]的大小,那么子序列的长度就会增长1。
在这之前要将dp中所有的数字初始化为1,因为递增子序列的长度至少为1。

LeetCode2 两数相加

题目

给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:

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

C++代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = new ListNode(0);
        ListNode *curr = head;
        int carry=0;//用于存储进位
        bool l1_finish=false;//用于判断第一个链表是否走完
        bool l2_finish=false;//用于判断第二个链表是否走完
        while(1)
        {
            int val_tmp;//临时存一下
            //三种情况,同时加两个,只加一个,记得每次加carry
            if(l1_finish==false && l2_finish==false)
                val_tmp = l1->val + l2->val + carry;
            else if(l1_finish==false && l2_finish==true)
                val_tmp = l1->val + carry;
            else if(l1_finish==true && l2_finish==false)
                val_tmp = l2->val + carry;

            carry = val_tmp/10;//存储进位
            ListNode *new_node = new ListNode(val_tmp%10);//生成一个新的节点
            curr->next = new_node;//append节点
            curr = curr->next;//移动游标
            //判断是否到头
            if(l1->next==NULL) 
                l1_finish = true;
            else l1 = l1->next;

            if(l2->next==NULL) 
                l2_finish = true;
            else l2 = l2->next;
            //结束循环,注意分情况讨论有无进位情况
            if(l1_finish && l2_finish && carry==0) break;
            else if(l1_finish && l2_finish && carry!=0)
            {
                ListNode *carry_node = new ListNode(carry);
                curr->next = carry_node;
                curr = curr->next;
                break;
            }
        }
        return head->next;//head是空哦,返回下一个值
    }
};

体会

的确是简单题,直接模拟!
但我写的好复杂啊!!!
应该是没写好,以后再改吧。
注意[5],[5]、[1,8],[0]这样的数据,有诈!

LeetCode328 奇偶链表

题目

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

C++代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == NULL || head->next==NULL) return head;//保护一下下 [],[1] 这两种情况

        ListNode *new_head = new ListNode(0);
        ListNode *curr = head;
        ListNode *curr_new = new_head;
        int index =0;
        while(curr)
        {
            if(curr->next&&curr->next->next)//1,2,3,4,5这种情况
            {
            ListNode *new_node = new ListNode(curr->next->val);
            curr_new->next = new_node;
            curr_new = curr_new->next;
            ListNode *tmp_delete = curr->next;//保护内存
            curr->next = curr->next->next;
            delete(tmp_delete);//保护内存
            curr = curr->next;
            if(curr->next ==NULL) break;//指针移动到5,直接break;
            }
            //1,2,3,4,5,6这种情况,最后的6要单独处理
            else if(curr->next && !curr->next->next){
                curr_new->next = curr->next;
                break;
            }
        }
        curr->next = new_head->next;
        return head;
    }
};

体会

遍历一遍就OK,遇到偶数存到新链表后删除,最后将两个链表连接。
重点考察链表的delete和append!

LeetCode3 无重复字符的最长子串

题目

给定一个字符串,找出不含有重复字符的最长子串的长度。
示例:
给定 “abcabcbb” ,没有重复字符的最长子串是 “abc” ,那么长度就是3。
给定 “bbbbb” ,最长的子串就是 “b” ,长度是1。
给定 “pwwkew” ,最长子串是 “wke” ,长度是3。请注意答案必须是一个子串,”pwke” 是 子序列 而不是子串。

C++代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        map<char,int> max_str;
        int start_index = 0;//用于记录当前字符串开始的位置
        map<char,int>::iterator it;
        int max_length = 0;//记得初始化
        for(int i=0;i<s.length();i++)
        {
            it = max_str.find(s[i]);//查找现在map中是否有重复的字符
            if(it!=max_str.end()&&it->second>=start_index)//如果有重复的字符,且该字符下标比当前最长字符串起始下标大,则修改起始下标。相当于左指针右移动。
            {
                start_index = it->second+1;//修改下标
            }
            max_str[s[i]] = i;//将当前的字符及下标存入map
            max_length = max(i-start_index+1,max_length);//计算最大长度
        }
        return max_length;
    }
};

体会

感谢STL
这道题的关键需要利用map
整个思路比较清晰
首先我们从头开始遍历字符串
如果当前字符没有在map中出现过,那么就将当前字符存入map,且不修改当前子串的起始位置。
如果当前字符在map中出现过,那么证明当前子串中出现了相同的字母,此时修改子串起始位置为map中与当前字符重复的字符的下标。

举例:
bacdb
i=0 s[i]=b it=’/0’ start_index = 0 max=1
i=1 s[i]=a it=’/0’ start_index = 0 max=2
i=2 s[i]=c it=’/0’ start_index = 0 max=3
i=3 s[i]=d it=’/0’ start_index = 0 max=4
i=4 s[i]=b it=’b’ start_index = 1 max=4
return max

猜你喜欢

转载自blog.csdn.net/HeiGe__/article/details/81198506
今日推荐