算法与数据结构:从一道题体会解算法题的过程

通过分析一道算法题,来不断优化代码,体会迭代的过程。题目如下:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

方法1:

说下思考过程,最先想到的是暴力求解,大概伪代码如下:

def merge(num1, m, num2, n):
    for i in range(n):
        for j in range(m):
            if num1[j] == num2[i]: #这里显然没有利用有序列表这个信息量
                num1[j] = num2[i] # 如何让num2[i]插入到这个j位置,而且num1里j位置后的元素都要移动。时间复杂度是多少?

这种方法的时间复杂度太高,是m*n级的,正如上面注释所言,这种解题没有充分利用题目给的更多信息,比如数组有序。

方法2:

考虑利用双指针法,我把我错误的代码也贴出来,算是代码草稿吧,当初用git管理了历史。最初版我是这样写的:

def merge(num1, m, num2, n):
    fast = 0
    n_1, n_2 = 0, 0
    mergedList = [None] * (m+n)
    while fast < (m+n):
        while num1[n_1] <= num2[n_2]  and n_1<m:
            list[fast] = num1[n_1]
            n_1 += 1
            fast += 1
        mergedList[fast] = num2[n_2]
        n_2 +=1
        fast += 1
    return mergedList

nums1 = [1,2,3, 4, 6, 11, 0,0,0]
m = 6
nums2 = [2,5,6]
n = 3
print(merge(nums1, m,nums2, n))

这段代码是通不过的,问题很多,但没关系,一开始把大概写出来,然后再一步步调试代码精修。这段代码问题太多,因为是草稿嘛。
问题如下:
1while循环写得太乱。
2没有考虑更多情况,比如有一个数组为空。
3要补上其中一个列表没有被迭代的最后一些元素。
优化代码如下:

def merge(nums1, m, nums2, n):
    fast = 0
    n_1, n_2 = 0, 0
    merged = [None] * (m + n)
    
    if m == 0:
        return nums2[:n]
    elif n == 0:
        return nums1[:m]
    
    while fast < (m + n):
        if n_1 < m and (n_2 >= n or nums1[n_1] <= nums2[n_2]):
            merged[fast] = nums1[n_1]
            n_1 += 1
        else:
            merged[fast] = nums2[n_2]
            n_2 += 1
        fast += 1
    
    return merged

nums1 = [1, 2, 3, 4, 6, 11, 0, 0, 0]
m = 6
nums2 = [2, 5, 6]
n = 3
print(merge(nums1, m, nums2, n))

这段代码还能优化吗?当然,优化空间大多数人都能看出来,还有其它可以优化的吗?
对于这段代码,可以从以下几个方面进行优化:

1优化空间复杂度:当前的代码创建了一个长度为 m+n 的列表 merged,用于存储合并后的结果。由于题目要求将结果存储在 nums1 中,可以考虑在原数组上直接进行合并,而不需要额外的空间。可以从末尾开始遍历数组,依次比较两个数组的元素,然后将较大的元素放到 nums1 的末尾。
2提前结束循环:在当前代码中,使用了一个 while 循环来遍历数组,但是可以通过添加额外的判断条件来提前结束循环。例如,当 n_1 达到 m 的位置时,可以直接将 nums2 中剩余的元素复制到 nums1 的末尾,并结束循环。
3减少不必要的判断:当前代码中,每次循环都会进行多个判断条件,包括 n_1 < m 和 n_2 < n。可以通过重新组织代码结构,将这些判断条件合并,减少不必要的判断。

优化后代码如下:

def merge(nums1, m, nums2, n):
    p1 = m - 1
    p2 = n - 1
    p = m + n - 1

    while p1 >= 0 and p2 >= 0:
        if nums1[p1] >= nums2[p2]:
            nums1[p] = nums1[p1]
            p1 -= 1
        else:
            nums1[p] = nums2[p2]
            p2 -= 1
        p -= 1

    if p2 >= 0:
        nums1[:p2+1] = nums2[:p2+1]

    return nums1

nums1 = [1, 2, 3, 4, 6, 11, 0, 0, 0]
m = 6
nums2 = [2, 5, 6]
n = 3
print(merge(nums1, m, nums2, n))

总结:算法题目,答案不重要,中间的思考过程,思维方式才值得记录下来。下一篇文章继续借这段题,扩展讲一下算法题的一些解决思路和技巧。

你可能感兴趣的:(算法)