【刷题日记】leetcode-321 拼接最大数

本篇尚未完成该题,后续完成的解题见:【刷题日记】成功再战《拼接最大数》 leetcode.321

一、题目

给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
说明: 请尽可能地优化你算法的时间和空间复杂度。
在这里插入图片描述

二、解题

解题思路:

(1)题目要求需要保证生成的数中各元素在原数组中的相对位置:这里想到同时遍历两个数组,保留当前最大的值。只要确保剩余元素足够组成后续的 k - 1 位数,当前解法就是最优的。
(2)数值范围在[0,9],遍历过程中取到9则可以退出
(3)按上述规则确定完当前位后,问题转化为在剩下的两个子数组中,找最大的k - 1 位数, 实际上变成了一个子问题,这里做递归即可。

根据上述的思路(应该算是贪心+递归?),做了以下编码:

class Solution {
    
    // 两个数组当前遍历位置指针
    int p1 = 0;
    int p2 = 0;
    int retp = 0;
    public int[] maxNumber(int[] nums1, int[] nums2, int k) {
        int[] ret = new int[k];
        getMaxKnums(nums1, nums2,k , ret);
        return ret;
    }
    // 递归求解满足条件的数组
    private void getMaxKnums(int[] nums1, int[] nums2, int k, int[] ret) {
        // 结束
        if(k == 0) {
            return;
        }
        // 归零
        p1 = 0;
        p2 = 0;
        // 遍历两个数组,选出当前最大值,直到剩余元素无法满足k的长度
        int max = -1;
        for(int i = 0; i < Math.max(nums1.length, nums2.length); ++i) {

            // 确认剩下元素个数即将不满足后续需求,退出循环
            if(k > nums1.length + nums2.length - p1 - p2) {
                break;
            }

            int currentMax;
            int currentNum1 = i >= nums1.length ? -1 : nums1[i];
            int currentNum2 = i >= nums2.length ? -1 : nums2[i];

            if(currentNum1 > currentNum2) {
                currentMax = currentNum1;
                if(max < currentMax) {
                    max = currentMax;
                    p1 = i + 1;
                    p2 = 0;
                }
            } else {
                currentMax = currentNum2;
                if(max < currentMax) {
                    max = currentMax;
                    p1 = 0;
                    p2 = i + 1;
                }
            }

            // 如果当前最大值是9,可以直接退出循环
            if(currentMax == 9) {
                break;
            }
        }

        ret[retp++] = max;

        // 从剩余元素中找出下一个最大值
        if(p1 > 0) {
            nums1 = Arrays.copyOfRange(nums1, p1, nums1.length);
        }
        if(p2 > 0) {
            nums2 = Arrays.copyOfRange(nums2, p2, nums2.length);
        }
        getMaxKnums(nums1, nums2, k - 1, ret);
    }
}

整体思路看起来还算比较清晰,但是在执行用例时却发现有些场景过不了。原因是在比较中如果两个值相等的情况下,我是直接选择了其中一个分支的数据作为结果。这样的话,就有可能无法得出最优解。
在这里插入图片描述
首先想到了既然当前位不能判断如何选择,那就分别看两种选择会产生怎样不同的结果,只要我找到后续的一个不同结果, 那么选择下一位大的值就是最优的。但这样做的问题也很明显,就是如果两个足够长的数组,每一位数字都是相同的,则每一位都会演化出两个分支,这个复杂度几乎是不能接受的。

所以找到一种方法,能够快速稳定的应对递归过程中碰到相同元素的问题就成了解题的关键。

由于自己硬是没能想出更好的办法,先硬着头皮把不断分叉的方案实现了一下。
改造过程中又改了几个地方的实现:
(1)由于会有分叉逻辑,修改了原有retp全局指针的实现,改成局部变量来控制写入位置
(2)增加了确定要给最大值时的校验,只靠原有的校验是不能校验完全的

class Solution {
    
    // 两个数组当前遍历位置指针
    int p1 = 0;
    int p2 = 0;
    int retp = 0;
    public int[] maxNumber(int[] nums1, int[] nums2, int k) {
        int[] ret = new int[k];
        getMaxKnums(nums1, nums2,k , ret, k);
        return ret;
    }
    // 递归求解满足条件的数组
    private void getMaxKnums(int[] nums1, int[] nums2, int k, int[] ret, final int total) {
        // 结束
        if(k == 0) {
            return;
        }
        p1 = 0;
        p2 = 0;
        // 遍历两个数组,选出当前最大值,直到剩余元素无法满足k的长度
        int max = -1;
        // 当前最优解是相等的
        int maxPointEqual = -1;
        for(int i = 0; i < Math.max(nums1.length, nums2.length); ++i) {

            // 确认剩下元素个数即将不满足后续需求,退出循环
            if(k > nums1.length + nums2.length - p1 - p2) {
                break;
            }

            int currentMax;
            int currentNum1 = i >= nums1.length ? -1 : nums1[i];
            int currentNum2 = i >= nums2.length ? -1 : nums2[i];

            if(currentNum1 > currentNum2) {
                currentMax = currentNum1;
                if(max < currentMax) {
                    // 加判断:选这个是否会导致元素不够,不够就break
                    if(k > nums1.length + nums2.length - i) {
                        break;
                    }
                    maxPointEqual = -1;
                    max = currentMax;
                    p1 = i + 1;
                    p2 = 0;
                }
            } else if(currentNum1 < currentNum2) {
                currentMax = currentNum2;
                if(max < currentMax) {
                    // 加判断:选这个是否会导致元素不够,不够就break
                    if(k > nums1.length + nums2.length - i) {
                        break;
                    }
                    maxPointEqual = -1;
                    max = currentMax;
                    p1 = 0;
                    p2 = i + 1;
                }
            } else {
                // 相等场景,打标记录
                currentMax = currentNum2;
                if(max < currentMax) {
                    // 加判断:选这个是否会导致元素不够,不够就break
                    if(k > nums1.length + nums2.length - i) {
                        break;
                    }
                    maxPointEqual = i;
                    // 过程中的相等先随机选个方向往下走
                    max = currentMax;
                    p1 = 0;
                    p2 = i + 1;
                }
            }

            // 如果当前最大值是9,可以直接退出循环
            if(currentMax == 9) {
                break;
            }
        }


        // 判断当前最优解是否为相等位
        if(maxPointEqual >= 0) {
            ret[total - k] = max;
            // 分别走两个分支计算
            int[] result = new int[k - 1];
            int[] ret1 = new int[k - 1];
            int[] ret2 = new int[k - 1];
            getMaxKnums(Arrays.copyOfRange(nums1, maxPointEqual + 1, nums1.length), nums2, k - 1, ret1, k - 1);
            getMaxKnums(nums1, Arrays.copyOfRange(nums2, maxPointEqual + 1, nums2.length), k - 1, ret2, k - 1);
            // 对结果做比较后决定
            boolean isDiff = false;
            for(int i = 0; i < ret1.length; ++i) {
                if(ret1[i] == ret2[i]) {
                    continue;
                }
                result = ret1[i] > ret2[i] ? ret1 :ret2;
                isDiff = true;
                break;
            }
            // 完全一样,直接选一个作为结果
            if(!isDiff) {
                result = ret1;
            }

            // 直接得到最优解数组
            System.arraycopy(result,0,ret, ret.length - result.length, result.length);
        } else {
            ret[total - k] = max;
            // 从剩余元素中找出下一个最大值
            if(p1 > 0) {
                nums1 = Arrays.copyOfRange(nums1, p1, nums1.length);
            }
            if(p2 > 0) {
                nums2 = Arrays.copyOfRange(nums2, p2, nums2.length);
            }
            getMaxKnums(nums1, nums2, k - 1, ret, total);
        }
    }
}

执行结果:
几乎就要通过了!!这用例的复杂度真是对测试不友好。。。
在这里插入图片描述
根据调试分析,发现正从数组1中拿了一个9,剩下的9都是从数组2拿的。这是由于数组一中,第1个9和第2个9中间相隔的元素太多,而上面贪心的策略是总是拿最近的一个最大值。导致最后数组2只剩 [7,8,0,7,2,3],而再往下遍历时,虽然数组1还有9,但是因为取完之后总长度不足而被放弃了。

到这里,大概可以给出结论是,这个贪心解法是不完美的,无法获取全局最优解。

三、题解

由于花了太多的时间在上面的方式上了,有点得不偿失,求助题解:
1、官方题解
2、同类相关题型题解

困难题还是困难题,年轻人耗子尾汁。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页