21. 合并两个有序链表 23. 合并K个排序链表(递归和迭代) 14. 最长公共前缀(四种思路解析)

21. 合并两个有序链表

题目:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

解法:

/**
 * 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (NULL == l1)
            return l2;
        if (NULL == l2)
            return l1;
        
        ListNode dummy(0);
        ListNode *head = &dummy;
        while (l1 && l2) {
            if (l1->val > l2->val) {
                head->next = l2;
                l2 = l2->next;
            } else {
                head->next = l1;
                l1 = l1->next;
            }
            head = head->next;
        }
        head->next = l1 ? l1 : l2;

        return dummy.next;
    }
};

23. 合并K个排序链表

题目:

合并 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

解法:递归两两合并

class Solution {
private:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (NULL == l1)
            return l2;
        if (NULL == l2)
            return l1;
        
        ListNode dummy(0);
        ListNode *head = &dummy;
        while (l1 && l2) {
            if (l1->val > l2->val) {
                head->next = l2;
                l2 = l2->next;
            } else {
                head->next = l1;
                l1 = l1->next;
            }
            head = head->next;
        }
        head->next = l1 ? l1 : l2;

        return dummy.next;
    }
    ListNode* merge(vector<ListNode*>& lists, int left, int right) {
        if (left == right)
            return lists[left];
        if (left > right)
            return NULL;

        int mid = (left + right)>>1;
        return mergeTwoLists(merge(lists, left, mid), merge(lists, mid+1, right));
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size()-1);
    }
};

解法2:非递归两两合并

class Solution {
private:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (NULL == l1)
            return l2;
        if (NULL == l2)
            return l1;
        
        ListNode dummy(0);
        ListNode *head = &dummy;
        while (l1 && l2) {
            if (l1->val > l2->val) {
                head->next = l2;
                l2 = l2->next;
            } else {
                head->next = l1;
                l1 = l1->next;
            }
            head = head->next;
        }
        head->next = l1 ? l1 : l2;

        return dummy.next;
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int len = lists.size();
        if (len == 0)
            return NULL;

        int size = 1;
        while (size < len) {
            for (int i = 0; i < len-size; i += 2*size)            //i < len-size,避免奇偶个链表引起访问越界
                lists[i] = mergeTwoLists(lists[i], lists[i+size]);
            size <<= 1;
        }
        
        return lists[0];
    }
};

            多个链表合并,可以用多个方法实现两两合并,例如双向列队,一边出,一边压入,只有一个链表时返回。尽量避免链表长度悬殊的链表依次合并,效率较差。

14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

示例 1:

输入: ["flower","flow","flight"]
输出: "fl"
示例 2:

输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。

 思路:

之所以把这个题目放到这里,是觉得该题目的思路和23的思路有一定的相似度,并且时间复杂度的分析也如出一辙。

该题目的解法有很多,例如:

  • 横向扫描:strs[0]和strs[1]查找最大前缀prefix1,再用prefix1和strs[2]找最大前缀prefix2,以此类推。这种最差情况下,效率很差,例如前面的串共同前缀很长,最后一个串和前面的串没有共同前缀。
  •  纵向扫描:依次检查字符串的第i个字符,若遇见不同,字符串前 i-1个字符就是最长前缀。这种解法如果没有前提处理,为判断字符下标有效性,会在各个字符串的长度上有重复的计算。为此,以一个最短的字符串为基准找最长前缀,是一个不错的想法。
  • 分治:就是横向扫描策略的变种,不是从左到右依次找最长前缀,而是strs[0]和strs[1],strs[2]和strs[3]...各找到最长前缀prefix1,prefix2...,然后再重复步骤,找出最终的prefix。
  • 二分查找:找出最短字符串stri,二分该字符串stri[0-mid],stri[mid-len],若stri[0-mid]是最长前缀的一部分,再在stri[mid-len]中二分,和前匹配串连接,再匹配,直至找到完全匹配的最长前缀。这种解法是基于最短字符串,在对字符串不断二分,实际上的字符比较次数是最多的。

综上,一个重要前提是先找到最短子串,这个能节省很多边界检查,然后再采取各种策略找到最终结果。各种解法的代码参考最长公共前缀。这里给出纵向扫描的详细代码。

解法1:纵向扫描

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int len = strs.size();
        if (len == 0)
            return "";
        if (len == 1)
            return strs[0];

        //find the shortest str, swap to the first str of the vector.
        int minLenIndex = 0;
        int minLen = strs[0].size();
        for (int i = 1 ;i < len; ++i) {
            int theLen = strs[i].size();
            if (theLen < minLen) {
                minLen = theLen;
                minLenIndex = i;
            }
        }
        if (minLenIndex != 0)
            swap(strs[0], strs[minLenIndex]);

        //find the longest prefix.
        bool flag = false;
        for (int i = 0; i < minLen && !flag; ++i) {
            for (int j = 1; j < len; ++j) {
                if (strs[0][i] != strs[j][i]) {
                    flag = true;
                    strs[0][i] = '\0';
                    break;
                }
            }
        }

        return strs[0];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值