【leetcode】算法题记录(151-160)

翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。

说明:

  • 无空格字符构成一个 单词 。
  • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
  • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
示例 1:

输入:"the sky is blue"
输出:"blue is sky the"

示例 2:

输入:"  hello world!  "
输出:"world! hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入:"a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

示例 4:

输入:s = "  Bob    Loves  Alice   "
输出:"Alice Loves Bob"

示例 5:

输入:s = "Alice does not even like bob"
输出:"bob like even not does Alice"

提示:

    1 <= s.length <= 104
    s 包含英文大小写字母、数字和空格 ' '
    s 中 至少存在一个 单词

进阶:

    请尝试使用 O(1) 额外空间复杂度的原地解法。

解题思路:因为需要原地解法,所以不能申请额外的空间去存储字符串,只能在原字符串上面修改。所以想到的就是利用两次反转实现翻转。然后再次遍历删掉多余的空格。

class Solution {
public:
    string reverseWords(string s) {
        int i = 0, j = s.size()-1;
        //第一次将整个字符串反转
        while(i < j) {
            char tmp = s[i];
            s[i] = s[j];
            s[j] = tmp;
            i++, j--;
        }
        i = 0, j = 0;
        //第二次依次将每个单词反转
        while(true) {
            while(s[j] != ' ' && j < s.size()) j++;
            int end = j;
            j--;
            while(i < j) {
                char tmp = s[i];
                s[i] = s[j];
                s[j] = tmp;
                i++, j--;
            }
            i = end;
            while(s[i] == ' ' && i < s.size()) i++;
            if(i == s.size()) break;
            j = i;
        }
        bool blank = false;
        //删除多余1次的空格
        for(auto it = s.begin(); it != s.end();) {
            if(blank && *it == ' ') {
                it = s.erase(it);
            }else {
                if(*it == ' ') blank = true;
                else blank = false;
                ++it;
            }
        }
        //删除头尾空格
        if(s[0] == ' ') s.erase(s.begin());
        if(s[s.size() -1] == ' ') s.erase(s.end()-1);
        return s;
    }
};

乘积最大子数组

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

示例 1:

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

示例 2:

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

解题思路:以 0 为界,计算每一个连续数组的乘积,记录最大值。

遍历规则如下:

  • 如果该连续数组乘积是正数,则可以直接比较;
  • 若该连续数组是负数,则该连续数组的最大值只有可能会出现在四个位置上面,即 第一个负数前的乘积,第一个负数后的乘积,最后一个负数前的乘积,最后一个负数后的乘积。

虽然这么做是通过解题了,但是我发现在实操时有很多需要注意的小细节,所以感觉思路好像是不是不正确。

最后也是磕磕绊绊的 AC 了,不过也算是 常数空间复杂度,时间复杂度 O(n),也算不错了。

后面看题解发现,终究是错付了,正确动作应该是动态规划。因为有负数的存在,所以需要记录某点处的最大值和最小值。

动态规划两步走:

  • 初始化,maxF[0] = nums[0], minF[0] = nums[0], ans = nums[0];
  • 动态转移,
    • 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]));
    • ans = max(ans, maxF[i]);
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int res = nums[0];
        int subres = 0;
        bool start = false;
        int tmp1 = 0, tmp2 = 0, tmp3 = 0, tmp4 = 0, num = 0;
        for(int i = 0; i < nums.size(); ++i) {
            if(nums[i] != 0 ) {
                if(!start) {
                    subres = nums[i];
                    start = true;
                } else subres *= nums[i];
                num++;
                if(nums[i] < 0) {
                    if(tmp1 == 0) {
                        tmp1 = (num == 1) ? subres : subres / nums[i];
                        tmp2 = subres;
                        tmp3 = tmp1;
                        tmp4 = tmp2;
                    } else {
                        tmp3 = subres / nums[i];
                        tmp4 = subres;
                    }
                }

            }
            if(nums[i] == 0 || i == nums.size() - 1) {
                if(subres >= 0) {
                    res = max(res, subres);
                }else {
                    int max1 = num == 1 ? tmp1 : max(tmp1, subres/tmp2);
                    int max2 = num == 1 ? tmp1 : max(tmp3, subres/tmp4);
                    res = max(res, max(max1, max2));
                }
                tmp1 = 0, tmp2 = 0, tmp3 = 0, tmp4 = 0, num = 0, subres = 0;
                start = false;
                
            }
        }
        return res;
    }
};

寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

    n == nums.length
    1 <= n <= 5000
    -5000 <= nums[i] <= 5000
    nums 中的所有整数 互不相同
    nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

解题思路:查找有序数组最常使用的就是利用二分法去解决,所以其实这道题就是查找数组的最小值。

class Solution {
public:
    int subFind(vector<int>& nums, int start, int end) {
        int mid = start + (end - start) / 2;
        int res = -1;
        if(start == end) res = start;
        else if (nums[mid] > nums[end]) {
            res = subFind(nums, mid + 1, end);
        }else {
            res = subFind(nums, start, mid);
        }
        return res;
    }

    int findMin(vector<int>& nums) {
        int res = subFind(nums, 0, nums.size()-1);
        // cout << res << " " << nums[res] << endl;
        return ((res == -1) ? nums[0] : nums[res]);
    }
};

寻找旋转排序数组中的最小值 II

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
  • 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

示例 1:

输入:nums = [1,3,5]
输出:1

示例 2:

输入:nums = [2,2,2,0,1]
输出:0

提示:

    n == nums.length
    1 <= n <= 5000
    -5000 <= nums[i] <= 5000
    nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

进阶:

    这道题是 寻找旋转排序数组中的最小值 的延伸题目。
    允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

解题思路:这道题和上一道题本质上是一样的,但是需要注意的就是有重复值的存在,所以在切割的时候,不能简单的比较大小,还得比较相等的情况。

如果中值和尾值相等,那么将尾往后移一位继续二分。

class Solution {
public:    
    int subFind(vector<int>& nums, int start, int end) {
        int mid = start + (end - start) / 2;
        int res = -1;
        if(start == end) res = start;
        else if (nums[mid] > nums[end]) {
            res = subFind(nums, mid + 1, end);
        }else if (nums[mid] < nums[end]){
            res = subFind(nums, start, mid);
        }else {
            res = subFind(nums, start, end-1);
        }
        return res;
    }

    int findMin(vector<int>& nums) {
        int res = subFind(nums, 0, nums.size()-1);
        // cout << res << " " << nums[res] << endl;
        return ((res == -1) ? nums[0] : nums[res]);
    }
};

最小栈

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

  • 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.

提示:

    pop、top 和 getMin 操作总是在 非空栈 上调用。

解题思路:直接利用栈和动态规划的思想,保存当前的 val,以及最小值,so easy!

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */
class MinStack {
public:
    /** initialize your data structure here. */

    stack<pair<int, int>> mST;

    MinStack() {
    }
    
    void push(int val) {
        pair<int, int> it;
        if(mST.empty()) {
            it = make_pair(val, val);
        }else {
            it = make_pair(val, min(val, getMin()));
        }
        mST.push(it);
    }
    
    void pop() {
        mST.pop();
    }
    
    int top() {
        return mST.top().first;
    }
    
    int getMin() {
        return mST.top().second;
    }
};

上下翻转二叉树

用 Read4 读取 N 个字符

用 Read4 读取 N 个字符 II

至多包含两个不同字符的最长子串

会员专享,作为白嫖小王子当然选择跳过

相交链表

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

示例 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) 内存。

解题思路:首先遍历两个数组得到两个数组的长度,并且判断这两个数组的未节点是否是同一个,如果不是的话直接返回 NULL。

找到两个数组的长度之后,先让长的链表先走相差的节点,然后再一起遍历,找到第一个相同的节点返回。这样做的时间复杂度为 O(max(m, n)), 空间复杂度为 O(1);

ps: 看完解题发现可以用更优雅的方法去解决问题,就是利用双指针法;

  • 用 a+c 表示链表 A, b+c 表示链表 B, 其中 c 为 A 和 B 相同的子链表,且 c 长度不可能为 0,因为有 NULL 节点的存在,c 长度至少为1。
  • 这样会发现一个神奇的现象: a+c+b+c = b+c+a+c,这在数学中其实就是交换律。
  • 根据这个等式可以发现不管 a 和 b 的长度是否相等,只要按照这个等式,两个指针一定会在 c 的头结点相遇。
  • 漂亮!
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lA = 0, lB = 0;
        ListNode *scanA = headA, *scanB = headB;
        while(scanA->next || scanB->next) {
            if(scanA->next) {
                lA++;
                scanA = scanA->next;
            }
            if(scanB->next) {
                lB++;
                scanB = scanB->next;
            }
        }
        if(scanA != scanB) return NULL;
        ListNode *shortN, *longN;
        if(lA > lB) {
            shortN = headB, longN = headA;
        }else {
            shortN = headA, longN = headB;
            int tmp = lA;lA = lB; lB =tmp;
        }
        while(lA >= 0) {
            if(lA > lB) longN = longN->next;
            else {
                if(longN == shortN) return longN;
                longN = longN->next;
                shortN = shortN->next;
            }
            lA--;
        }
        return NULL;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值