LeetCode hot-100 简单and中等难度,51-60.

142. 环形链表 II

难度中等570收藏分享切换为英文关注反馈

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

说明:不允许修改给定的链表。

 

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

 

进阶:
你是否可以不用额外空间解决此题?

双指针第一次相遇: 设两指针 fast,slow 指向链表头部 head,fast 每轮走 222 步,slow 每轮走 111 步;


第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;

TIPS: 若有环,两指针一定会相遇。因为每走 111 轮,fast 与 slow 的间距 +1,fast 终会追上 slow;

 

第二种结果: 当fast == slow时, 两指针在环中 第一次相遇 。下面分析此时fast 与 slow走过的 步数关系 :

设链表共有 a+b 个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环 有 bbb 个节点(这里需要注意,aaa 和 bbb 是未知数,例如图解上链表 a=4 , b=5);设两指针分别走了f,s 步,则有:


1、fast 走的步数是slow步数的 2 倍,即 f=2s;(解析: fast 每轮走 2 步)
2、fast 比 slow多走了 nnn 个环的长度,即 f=s+nbf = s + nbf=s+nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍 );


以上两式相减得:f=2nb,s=nb,即fast和slow 指针分别走了 2n,n 个 环的周长 (注意: n 是未知数,不同链表的情况不同)。

总结关键点:

  • 1.第一次相遇,slow = nb
  • 2.a+nb = 入口点
  • 3.slow再走a = 入口 = head走到入口 = a
  • 4.由3得出,起始距离入口 = 第一次相遇位置 + a

作者:jyd
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
来源:力扣(LeetCode)


class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head, *p = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast)            //如果链表存在环
            {
                while(p != slow)
                {
                    p = p->next;
                    slow = slow->next;
                }
                return p;
            }
        }
        return NULL;
    }
};

 

146. LRU缓存机制

难度中等799

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put

获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

 

进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?

 

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得关键字 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得关键字 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4
实现本题的两种操作,需要用到一个哈希表和一个双向链表。
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在
缓存中的键值对。

注意:
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的
键值对是最久未使用的。
哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

原因:
在O(1)时间内get到已存的值,可以使用哈希表,而哈希表存储键值是没有先后顺序的,因此就不
能够在O(1)的时间内删除最久未使用的元素,可以采用双向链表,链表的优点是插入删除元素快,
而且维护键值的先后顺序,我们结合哈希表和双向链表的优势,用哈希表结合双向链表方式实现LRU。

使用哈希表以关键字为key,链表结点为value,双向链表维护关键字的先后顺序,新加入的或者最近
使用过的关键字都插入或移动到链表头部,而如果当前缓存数量超过了容量,就移除一个链表尾部的元
素,它就是最久未使用的元素。

小贴士
在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添
加节点和删除节点的时候就不需要检查相邻的节点是否存在。



class LRUCache{
private:
    unordered_map<int,Node*> cache;
    Node* head;
    Node* tail;
    int size;
    int capacity;
public:
    LRUCache(int _capacity):capacity(_capacity),size(0){
        head=new Node();
        tail=new Node();
        head->next=tail;tail->pre=head;
    }
    int get(int key){
        if(!cache.count(key)) return -1;
        Node* node=cache[key];
        moveToHead(node);
        return node->val;
    }
    void put(int key,int val){
        if(!cache.count(key)){
            Node* node=new Node(key,val);
            cache[key]=node;
            addToHead(node);
            size++;
            if(size>capacity){
                Node* removed=removeTail();
                cache.erase(removed->key);
                delete removed;
                size--;
            }
        }else{
            Node* node=cache[key];   
            node->val=val;
            moveToHead(node);
        }
    }
    void addToHead(Node* node){
        node->pre=head;
        node->next=head->next;
        head->next->pre=node;
        head->next=node;
    }
    void removeNode(Node* node){
        node->pre->next=node->next;
        node->next->pre=node->pre;
    }
    void moveToHead(Node* node){
        removeNode(node);
        addToHead(node);
    }
    Node* removeTail(){
        Node* node=tail->pre;
        removeNode(node);
        return node;
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

 

148. 排序链表

难度中等672收藏分享切换为英文关注反馈

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

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

示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5
归并排序
O(nlogn)
有迭代法和递归法。

递归做归并排序:
//找到链表的中点,快慢双指针法。
//合并merge

ListNode* sortList(ListNode* head){
    if(head==NULL||head->next==NULL) return head;
    ListNode* fast=head->next;
    ListNode* slow=head;
    while(fast!=NULL&&fast->next!=NULL){
        slow=slow->next;
        fast=fast->next->next;    
    }
    //分隔之后,发现奇数slow在中点,偶数在中点左边。
    ListNode* tmp=slow->next;
    slow->next=NULL;
    ListNode* left=sortList(head);
    ListNode* right=sortList(tmp);
    ListNode* h=new ListNode(0);
    ListNode* res=h;
    while(left!=NULL&&right!=NULL){
        if(left->val<right->val){
            h->next=left;left=left->next;
        }else{
            h->next=right;right=right->next;
        }
        h=h->next;
    }
    h->next=left!=NULL?left:right;
    return res->next;
}
迭代法:

ListNode* sortList(ListNode* head){
    ListNode* tmp=head;
    int len=0;
    while(tmp!=NULL){
        tmp=tmp->next;
        len++;
    }
    ListNode* dummy=new ListNode(-1);dummy->next=head;
    //迭代步长
    int step=1;
    ListNode *h1,*h2,*pre;
    while(step<len){
        //为了下一次迭代
        pre=dummy;
        tmp=dummy->next;
        while(tmp!=NULL){
            h1=tmp;
            int cnt=step;
            while(cnt>0&&tmp!=NULL){
                cnt--;
                tmp=tmp->next;
            }
            //如果h1没走完,那么直接跳出
            if(cnt>0||tmp==NULL) break;
            h2=tmp;
            cnt=step;
            while(cnt>0&&tmp!=NULL){
                cnt--;
                tmp=tmp->next;
            }
            int len1=step;int len2=step-cnt;
            while(len1>0&&len2>0){
                if(h1->val<=h2->val){
                    pre->next=h1;
                    h1=h1->next;len1--;
                }else{
                    pre->next=h2;
                    h2=h2->next;len2--;
                }
                pre=pre->next;
            }
            while(len1>0){
                pre->next=h1;h1=h1->next;len1--;
                pre=pre->next;
            }
            while(len2>0){
                pre->next=h2;h2=h2->next;len2--;
                pre=pre->next;
            }
            pre->next=tmp;
        }
        step*=2;
    }    
    return dummy->next;

}

152. 乘积最大子数组

难度中等696

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

 

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

标签:动态规划
遍历数组时计算当前最大值,不断更新
令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i])
当负数出现时则imax与imin进行交换再进行下一步计算
时间复杂度:O(n)

作者:guanpengchn
链接:https://leetcode-cn.com/problems/maximum-product-subarray/solution/hua-jie-suan-fa-152-cheng-ji-zui-da-zi-xu-lie-by-g/

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        vector <int> maxF(nums), minF(nums);
        for (int i = 1; i < nums.size(); ++i) {
            maxF[i] = max(maxF[i - 1] * nums[i], max(nums[i], minF[i - 1] * nums[i]));
            minF[i] = min(minF[i - 1] * nums[i], min(nums[i], maxF[i - 1] * nums[i]));
        }
        return *max_element(maxF.begin(), maxF.end());
    }
};

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int maxF = nums[0], minF = nums[0], ans = nums[0];
        for (int i = 1; i < nums.size(); ++i) {
            int mx = maxF, mn = minF;
            maxF = max(mx * nums[i], max(nums[i], mn * nums[i]));
            minF = min(mn * nums[i], min(nums[i], mx * nums[i]));
            ans = max(maxF, ans);
        }
        return ans;
    }
};

155. 最小栈

难度简单634

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • getMin() —— 检索栈中的最小元素。

 

示例:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

辅助栈(同时保存值和对应的最小值)

算法
按照上面的思路,我们只需要设计一个数据结构,使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。


当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;

当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出;

在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/min-stack/solution/zui-xiao-zhan-by-leetcode-solution/

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s;
    stack<int> min_s;
    MinStack() {
        min_s.push(INT_MAX);
    }
    
    void push(int x) {
        s.push(x);
        min_s.push(min(x,min_s.top()));
    }
    
    void pop() {
        s.pop();
        min_s.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return min_s.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

 

也有一些其他解法,参考

作者:windliang
链接:https://leetcode-cn.com/problems/min-stack/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-38/

//当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。
当这个最小值要出栈的时候,下一个值便是之前的最小值了。

class MinStack {
    int min = Integer.MAX_VALUE;
    Stack<Integer> stack = new Stack<Integer>();
    public void push(int x) {
        //当前值更小
        if(x <= min){   
            //将之前的最小值保存
            stack.push(min);
            //更新最小值
            min=x;
        }
        stack.push(x);
    }

    public void pop() {
        //如果弹出的值是最小值,那么将下一个元素更新为最小值
        if(stack.pop() == min) {
            min=stack.pop();
        }
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return min;
    }
}

160. 相交链表

难度简单766

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表

在节点 c1 开始相交。

 

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

 

示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

 

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

 

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

我们需要做的事情是,让两个链表从同距离末尾同等距离的位置开始遍历。这个位置只能是较短链表的头结点位置。
为此,我们必须消除两个链表的长度差

指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历
如果 pA 到了末尾,则 pA = headB 继续遍历
如果 pB 到了末尾,则 pB = headA 继续遍历
比较长的链表指针指向较短链表head时,长度差就消除了
如此,只需要将最短链表遍历两次即可找到位置

可以理解成两个人速度一致, 走过的路程一致。那么肯定会同一个时间点到达终点。如果到达终点的最后一段路两人都走的话,那么这段路上俩人肯定是肩并肩手牵手的。 nb

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode pA = headA, pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}

作者:reals
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/tu-jie-xiang-jiao-lian-biao-by-user7208t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

169. 多数元素

难度简单688

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

 

示例 1:

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

示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int,int> mp;
        for(int i=0;i<nums.size();i++){
            mp[nums[i]]++;
            if(mp[nums[i]]>nums.size()/2) return nums[i];
        }
        return 0;
    }
};

198. 打家劫舍

难度简单1004

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

 

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。
class Solution {
public:
    // int rob(vector<int>& nums) {
    //     if(nums.size()==0) return 0;
    //     if(nums.size()==1) return nums[0];
    //     int n=nums.size();
    //     vector<int> v(n);
    //     v[0]=nums[0];
    //     v[1]=max(nums[0],nums[1]);
    //     for(int i=2;i<n;i++){
    //         v[i]=max(v[i-2]+nums[i],v[i-1]);
    //     }
    //     return v[n-1];
    // }
    int rob(vector<int> &nums) {
        int n = nums.size();
        // 记录 dp[i+1] 和 dp[i+2]
        int dp_i_1 = 0, dp_i_2 = 0;
        // 记录 dp[i]
        int dp_i = 0; 
        for (int i = n - 1; i >= 0; i--) {
            dp_i = max(dp_i_1, nums[i] + dp_i_2);
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i;
    }
};

 

200. 岛屿数量

难度中等696

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

 

示例 1:

输入:
[
['1','1','1','1','0'],
['1','1','0','1','0'],
['1','1','0','0','0'],
['0','0','0','0','0']
]
输出: 1

示例 2:

输入:
[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
class Solution {
public:
    int t[4][2]={{0,1},{-1,0},{0,-1},{1,0}};
    void get(vector<vector<char>>& grid,vector<vector<bool>> &visit,int x,int y,int n,int m){
        if(x<0||y<0||x>n||y>m||visit[x][y]||grid[x][y]=='0') return;
        visit[x][y]=true;
        for(int i=0;i<4;i++){
            int tx=x+t[i][0];
            int ty=y+t[i][1];
            if(tx<0||ty<0||tx>n||ty>m||visit[tx][ty]||grid[tx][ty]=='0') continue;
            get(grid,visit,tx,ty,n,m);
        }
    }
    int numIslands(vector<vector<char>>& grid) {
        if(grid.size()==0) return 0;
        vector<vector<bool>> visit(grid.size(),vector<bool>(grid[0].size(),false));
        int n=grid.size();int m=grid[0].size();
        int ans=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(visit[i][j]==false&&grid[i][j]=='1'){
                    // cout<<i<<j<<endl;
                    get(grid,visit,i,j,n-1,m-1);
                    ans++;
                }
            }
        }
        return ans;
    }
};

 

206. 反转链表

难度简单1154

反转一个单链表。

示例:

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

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //就是让head->next直接改变看看呢?
        ListNode* prev = NULL;
        ListNode* curr = head;
        while (curr != NULL) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;

        }
};

 

 


class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        /*
        //就是让head->next直接改变看看呢?
        ListNode* prev = NULL;
        ListNode* curr = head;
        while (curr != NULL) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
        */

        //递归
            if(head==NULL||head->next==NULL) return;
            ListNode* p = reverseList(head->next);
            head.next.next = head;
            head.next = NULL;//此处只对开头结点有效
            return p;
        }
};

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值