23. Merge k Sorted Lists等题 第二周解题报告

原题

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

题意简洁明了,K个有序链表,归并成一个有序链表。马上想到的是,每次选出K个链表头中数值最小的那个,加到result链表上。
因此问题就变成了,如何在K个数字里,快速找到最小数,并重复这个操作n次(n为总节点数)。如果像合并两个链表一样纯粹地用比较的话,找最小数的复杂度是O(K*n),链表的更新操作为O(n),因此最后复杂度是O(K*n)。事实证明无法在给定时间内通过这题。
因此使用堆结构来维护K个链表头的大小关系结构,构造的最初最小堆的时间复杂度是O(log(n)),每次取出后补一个数,加上维护堆结构的复杂度后,总的复杂度为O(n*log(n)),而且每一次取空一个链表,这个n都会变小(因为K减小)。
有一个要注意的点是,空链表是当成无穷大的数,必须排到末端,然后把K减小1。但如果遇到[NULL,4,6]这样的情况,就会排成[4,NULL,6]这样的错误结果,导致数字6永远消失,并且会在NULL里取数据导致RE。因此在构建堆的时候,要把空的链表直接剔除。在维护堆的时候,发现新添加进来的链表头为NULL,直接与堆最后一位交换,就可以避免出现上述情况了。

代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int k = buildHeap(lists);
        ListNode* root = new ListNode(-1);
        ListNode* ret = root;

        while(k) {
            // for(auto x: lists) {
            //     if(x) {
            //         cout << x->val << " ";
            //     } else {
            //         cout << -1 << " ";
            //     }
            // } cout << endl;
            // cout << k << ", " << ((lists[0])?lists[0]->val:-1) << endl;
            root->next = lists[0];
            lists[0] = (lists[0])?lists[0]->next:NULL;
            root = root->next;
            if(lists[0] == NULL) {
               swap(lists[0], lists[k-1]);
               k--;
            }
            k = minHeap(lists, 0, k);
        }

        return ret->next;
    }

    int minHeap(vector<ListNode*>& lists, int index, int k) {
        int iMin, iLeft, iRight;
        set<ListNode*> emptyList;
        while(1) {
            iMin = index;
            iLeft = iMin*2 + 1;
            iRight = iLeft + 1;

            if(iLeft < k && lists[iLeft] && (lists[index]==NULL || lists[index]->val > lists[iLeft]->val)) {
                iMin = iLeft;
            }

            if(iRight < k && lists[iRight] && (lists[iMin]==NULL || lists[iMin]->val > lists[iRight]->val)) {
                iMin = iRight;
            }


            if(iMin != index) {
                if(lists[index] == NULL) {
                    emptyList.insert(lists[index]);
                }
                swap(lists[iMin], lists[index]);
                index = iMin;
            } else {
                break;
            }
        }
        return k-emptyList.size();
    }

    int buildHeap(vector<ListNode*>& lists) {
        vector<ListNode*> tmp;
        for(auto l : lists) {
            if(l) {
                tmp.push_back(l);
            }
        }
        lists = tmp;
        int k = lists.size();
        int par = floor((k-1)/2);
        for(int i=par; i>=0; i--) {
            k = minHeap(lists, i, k);
        }
        return k;
    }
};

282. Expression Add Operators

原题

Given a string that contains only digits 0-9 and a target value, return all possibilities to add binary operators (not unary) +, -, or * between the digits so they evaluate to the target value.

Examples: 
"123", 6 -> ["1+2+3", "1*2*3"] 
"232", 8 -> ["2*3+2", "2+3*2"]
"105", 5 -> ["1*0+5","10-5"]
"00", 0 -> ["0+0", "0-0", "0*0"]
"3456237490", 9191 -> []

题意为给个字符串num和一个数字target,在num的各个字符中间插入+-*,使得算式的结果是target
明显的DFS,只是乘法在算式中有优先级,要如何在下一层深搜时抵消掉上一次的运算优先级错误。一次乘法只会对它上一次运算符产生影响,因此只需要记录上一层的运算结果和叠加值。如果这次使用乘法进行搜索,那么把上次的运算结果减去叠加值就行了。
还有就是记得剔除0开头的非法数字,比如00, 04。

代码

class Solution {
public:
    void dfs(vector<string>& result, string& num, int& target, string exp, int pos, long long now, long long prev) {
        // cout << pos << ", " << exp << " = " << now << endl;
        if(pos == num.size()) {
            if(target == now)
                result.push_back(exp);
            return;
        }

        for(int i=pos; i<num.size(); i++) {
            auto ns = num.substr(pos, i-pos+1);
            auto n = stoll(ns);
            // break zero
            if(num[pos] == '0' && i>pos)
                break;
            dfs(result, num, target, exp+"+"+ns, i+1, now+n, n);
            dfs(result, num, target, exp+"-"+ns, i+1, now-n, -n);
            // cout << now << ", " << prev << ", " << n << endl;
            dfs(result, num, target, exp+"*"+ns, i+1, now-prev+(prev*n), prev*n);
        }
    }

    vector<string> addOperators(string num, int target) {
        if(num.size() == 0) return vector<string>();
        if(num.size() == 1) return (stoi(num) == target? vector<string>(1, num): vector<string>());
        vector<string> ret;
        for(int i=0; i<num.size()-1; i++) {
            auto ns = num.substr(0, i+1);
            auto n = stoll(ns);
            if(num[0] == '0' && i) break;
            dfs(ret, num, target, ns, i+1, n, n);
        }

        if(stoll(num) == static_cast<long long>(target) && !(num[0] == '0' && num.size() > 1)) {
        ret.push_back(num);
    }
        // dfs(ret, num, target, "", 0, 0, 0); // take care of the begining
        return ret;
    }

};

53. Maximum Subarray

原题

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.

题意为在一个数组里找到一个子数组,它的元素和是其他子数组元素和的最大者。动态规划可以在O(n)的情况下解出。这样考虑,如果一个子数组是最大和的,那么不管把它的左边边界左移多少,新子数组的和肯定是变小或不变的,也就是说,新加进来的左边的子数组的和,必定是负数或零。例如上面的例子:假如已知最大的子数组为[4,-1,2,1],如果把它左边的两个数加进来,也就是加上[1, -3],新的子数组和肯定是变小的,也就是说新加进来的[1, -3]的元素和肯定是负数。
那么,我们从下标0开始叠加数字,每次更新最大值。如果当前叠加和变成了负数,那么舍去它,从下一个数字重新开始叠加,最后肯定能得到最大的元素和。
例如上面的例子,从-2开始叠加,叠加和如下变化:-2, 1, -3, 4, 3, 5, 6, 1, 5,最大值就为6。

代码

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxN = INT_MIN;
        int now = 0;
        for(auto n: nums) {
            if(now < 0) {
                now = 0;
            }
            now += n;
            maxN = max(maxN, now);
        }
        return maxN;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值