【leetcode】算法题记录(141-150)

环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

提示:

链表中节点的数目范围是 [0, 10^4]
-10^5 <= Node.val <= 10^5
pos 为 -1 或者链表中的一个 有效索引 。

解题思路,立马想到利用 set 存储节点是否被访问过,如果在遍历的过程中,再次被访问说明存在环。

但是看到题目要求用常量内存解决问题,那么就修改节点的值,把访问过的节点的 val 修改为 INT_MAX。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        while(head!=NULL) {
            if(head->val == INT_MAX) return true;
            head->val = INT_MAX;
            head = head->next;
        }
        return false;
    }
};

环形链表 II

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

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

进阶:

    你是否可以使用 O(1) 空间解决此题?

提示:

    链表中节点的数目范围在范围 [0, 10^4] 内
    -10^5 <= Node.val <= 10^5
    pos 的值为 -1 或者链表中的一个有效索引

解题思路:已经把上一题简单粗暴型的修改值方法给禁用了,又是使用常量空间,所以看样子就是指明只能使用快慢指针解决问题了。

快指针每次走两步,慢指针每次走一步,那么如果存在环,快慢指针终将会相遇。

假设 x 代表从 head 到入环点的距离,a 表示入环点到快慢指针相遇的距离,b 表示环剩余的距离。

可以得到 low 指针走的距离:x + a, fast 指针走的距离:x + (a + b)*n + a;

因为 fast 指针走的距离是 low 的两倍,可以得到 2*(x + a) = x + (a + b)*n + a,最终得到 x = b + (a + b) * (n - 1);

又因为 a+b 表示一环的长度,所以可以知道当快慢指针相遇时,此时派出一个节点从 head 出发,同时慢节点也开始转圈,这两个节点相遇时的节点就是入环点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* low = head;
        while(fast && fast->next && fast->next->next) {
            low = low->next;
            fast = fast->next->next;
            if(fast == low) {
                ListNode* res = head;
                while(res != low) {
                    res = res->next;
                    low = low->next;
                }
                return res;
            }
        }
        return NULL;
    }
};

重排链表

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

解题思路:利用 vector 遍历链表存储节点,再次遍历 vector 进行重新排列。

也可以用先把链表的后半段反转,然后合并前后半段得到答案。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 //利用vector链表
class Solution {
public:
    void reorderList(ListNode* head) {
        vector<ListNode*> vl;
        for(int i = 0; head != NULL; i++) {
            vl.push_back(head);
            head = head->next;
        }
        int i = 0, n = mil.size() - 1;
        while(i < mil.size()/2 ) {
            vl[i]->next = vl[n-i];
            vl[n-i]->next = vl[i+1];
            i++;
        }
        vl[mil.size()/2]->next = NULL;
    }
};

//反转合并链表
class Solution {
public:
    ListNode* findMid(ListNode* head) {
        ListNode* low = head;
        ListNode* fast = head;
        while(fast && fast->next && fast->next->next) {
            low = low->next;
            fast = fast->next->next;
        }
        //cout << low->val << endl;
        return low;
    }

    ListNode* revertNode(ListNode* root) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr != nullptr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    void mergeTwo(ListNode* fir, ListNode* sec) {
        while(sec) {
            ListNode* tmp = fir->next;
            fir->next = sec;
            sec = sec->next;
            fir->next->next = tmp;
            fir = tmp;
        }
        fir->next = NULL;
    }

    void reorderList(ListNode* head) {
        if(head && head->next) {
            ListNode* Pre = head;
            ListNode* RePre = revertNode(findMid(head)->next);
            mergeTwo(Pre, RePre);
        }
    }
};

二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

解题思路:递归解决二叉树的遍历问题

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> res;

    void visit(TreeNode* root) {
        if(root) {
            res.push_back(root->val);
            visit(root->left);
            visit(root->right);
        }
    }

    vector<int> preorderTraversal(TreeNode* root) {
        visit(root);
        return res;
    }
};

二叉树的后序遍历

给定一个二叉树,返回它的 后序 遍历。

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

解题思路:刚看到这道题和上一道题其实是推荐用迭代解决,其实迭代就是用 stack 解决问题。

需要注意的是在后序遍历的时候,从左节点访问到根节点时需要判断是否有访问过其右节点,如果没有访问过需要重新把根节点入栈,利用 prev 节点存储上一个访问的节点。

ps:Morris 遍历,有兴趣的可以去看一下,利用线索二叉树来降低空间复杂度。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stn;
        TreeNode* scan = root;
        TreeNode *prev = nullptr;
        while(scan || !stn.empty()) {
            while(scan) {
                stn.emplace(scan);
                scan = scan->left;
            }
            scan = stn.top();
            stn.pop();
            if (scan->right == nullptr || scan->right == prev) {
                res.emplace_back(scan->val);
                prev = scan;
                scan = nullptr;
            } else {
                stk.emplace(scan);
                scan = scan->right;
            }
        }
        return res;
    }
};

LRU 缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

 
提示:

    1 <= capacity <= 3000
    0 <= key <= 3000
    0 <= value <= 10^4
    最多调用 3 * 10^4 次 get 和 put

解题思路:利用 vector 数组来管理最近最少使用的数组,从 0 到数组末表示很少使用->最常使用,实现方法为每次 get 或者 put 都更新数组来保持使用频率。

利用 map 保存 key-value。

/**
 * 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);
 */
class LRUCache {
public:
    vector<int> mKey;
    int mCapacity = 0;
    unordered_map<int, int> mKeyValue;
    LRUCache(int capacity) {
        mCapacity = capacity;
    }
    
    int get(int key) {
        int res = -1;
        auto it = find(mKey.begin(), mKey.end(), key);
        if(it != mKey.end()) {
            mKey.erase(it);
            mKey.emplace_back(key);
            res = mKeyValue[key];
        }
        return res;
    }
    
    void put(int key, int value) {
        if(get(key) != -1) {
            mKeyValue[key] = value;
        }else {
            if(mKey.size() >= mCapacity) {
                int key = mKey[0];
                mKey.erase(mKey.begin());
                mKeyValue.erase(key);
            }
            mKey.emplace_back(key);
            mKeyValue[key] = value;
        }
    }
};

对链表进行插入排序

对链表进行插入排序。

插入排序算法:

  • 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  • 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  • 重复直到所有输入数据插入完为止。

解题思路:管理一个有序节点,这个节点是有序的,然后遍历未排序的节点,每次操作一个未排序的节点,把这个节点插入到有序的链表中。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        ListNode* sort = head;
        ListNode* ope = head->next;
        sort->next = nullptr;
        while(ope) {
            ListNode* unSort = ope->next;
            ListNode* sortScan = sort;
            ListNode* preScan = nullptr;
            while(sortScan && ope->val > sortScan->val) {
                preScan = sortScan;
                sortScan = sortScan->next;
            }
            if(!sortScan) {
                //sortScan 为 nullptr 插入有序链表尾部
                preScan->next = ope;
                ope->next = nullptr;
            }else if (!preScan) {
                //preScan 为 nullptr 插入有序链表头部
                ope->next = sort;
                sort = ope;
            }else {
                //插入到 preScan 和 sortScan 之间
                preScan->next = ope;
                ope->next = sortScan;
            }
            ope = unSort;
        }
        return sort;
    }
};

排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

进阶:

  • 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 1:

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

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]


提示:
    链表中节点的数目在范围 [0, 5 * 10^4] 内
    -10^5 <= Node.val <= 10^5

解题思路:看到这个时间复杂度,想到应该是二分来排序链表。至于怎么二分,关于链表有一个非常好用的方法来找到链表的中点,那就是快慢指针。

利用快慢指针来找到中点,那么就可以实现二分,然后递归排序前后两段,最后合并两个有序链表。但是利用递归的话空间复杂度是达不到常数级别的,陷入沉思。。。

怎样才能即利用二分来解决问题,有使得空间复杂度在常数空间呢,其实转换一个思路,递归版的二分是从 n -> n/2 -> … -> 1; 那么可以反过来,从 1 开始排序,

这样就会变成 1 -> 2 -> … -> n;这样也是实现了二分的思想,而且可以保证空间复杂度在常数级别。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        int length = 0;
        ListNode* tmp = head;
        while(tmp) {
            length++;
            tmp = tmp->next;
        }
        ListNode* res = new ListNode(-1, head);
        for(int i = 1; i < length; i *= 2) {
            ListNode *scan = res->next, *prev = res;
            while(scan) {
                ListNode* fir = scan;
                for(int j = 1; j < i && scan->next; j++) scan = scan->next;
                ListNode* sec = scan->next;
                scan->next = nullptr;
                scan = sec;
                for(int j = 1; j < i && scan && scan->next; j++) scan = scan->next;
                ListNode* next = nullptr;
                if(scan) {
                    next = scan->next;
                    scan->next = nullptr;
                }
                prev->next = merge(fir, sec);
                while(prev->next) prev = prev->next;
                scan = next;
            }
        }
        return res->next;
    }

    ListNode* merge(ListNode* fir, ListNode* sec) {
        ListNode* root = new ListNode(-1);
        ListNode* scan = root;
        while(fir && sec) {
            if(fir->val < sec->val) {
                scan->next = fir;
                fir = fir->next;
            }else {
                scan->next = sec;
                sec = sec->next;
            }
            scan = scan->next;
        }
        if(fir) scan->next = fir;
        if(sec) scan->next = sec;
        return root->next;
    }
};

直线上最多的点数

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

示例 1:

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4

示例 2:

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6

解题思路:直线方程是 y = kx+b,遍历数组构造直线并且记录 k 和 b,寻找最多个数。后面发现这道题的难点是精度问题,而不是思路。

由于求斜率需要用到除法,所以会导致精度出问题,那么最好的办法就是避免用除法,那么这样的话就无法保存斜率,直接遍历即可。

遍历的方法就是利用三个点是否共线,(x1 - x2) * (y3 - y1) == (y1 - y2) * (x3 - x1), 为了避免 int 相乘溢出,使用 long long 保存相乘结果。

class Solution {
public:
    int maxPoints(vector<vector<int>>& points) {
        if(points.size() < 3)  return points.size();
        int res = 0;
        for(int i = 0;i<points.size();++i) {
            int same = 1;
            for(int j = i+1;j < points.size(); ++j) {
                int count = 0;
                if(points[i][0] == points[j][0] && points[i][1] == points[j][1]) same++;
                else{
                    count++;
                    long long xDiff = (long long)(points[i][0] - points[j][0]);
                    long long yDiff = (long long)(points[i][1] - points[j][1]);
                    for (int k = j + 1; k < points.size(); k ++)
                        if (xDiff * (points[i][1] - points[k][1]) == yDiff * (points[i][0] - points[k][0]))
                            count++;
                }
                res = max(res, same + count);
            }
            if(res > points.size()/2 || res > points.size() - i) {
                break;
            }
        }
        return res;
    }
};

逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

    整数除法只保留整数部分。
    给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

 

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:
该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

 

提示:

    1 <= tokens.length <= 10^4
    tokens[i] 要么是一个算符("+"、"-"、"*" 或 "/"),要么是一个在范围 [-200, 200] 内的整数

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

解题思路:解题思路已经给出了,适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

而且相信如果有写过计算器的程序的经验会很熟悉这个,先是把计算器中的表达式中缀转后缀,然后利用栈计算结果。

class Solution {
public:
    int cacul(string ope, int fir, int sec) {
        int res = 0;
        if(ope == "+") {
            res = fir + sec;
        }else if(ope == "-") {
            res = sec - fir;
        }else if(ope == "*") {
            res = fir * sec;
        }else {
            res = sec / fir;
        }
        return res;
    }

    int evalRPN(vector<string>& tokens) {
        stack<int> res;
        for(int i = 0; i < tokens.size(); ++i) {
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                int fir = res.top();
                res.pop();
                int sec = res.top();
                res.pop();
                int tmp = cacul(tokens[i], fir, sec);
                res.push(tmp);
            }else {
                res.push(atoi(tokens[i].c_str()));
            }
        }
        return res.top();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值