LeetCode刷题笔记——LCP 73探险营地

题目简介与问题概述

探险家小扣在每次探险后都会留下一串记录,记录中包含了他访问过的营地名单。这些营地名以字符串形式出现,通过"->"符号连接。我们的任务是分析小扣的探险记录,找出他在哪一次探险中发现了最多的新营地。

探险记录是一个字符串数组,其中每个字符串代表一次探险的路径。初始探险的记录包含了小扣已知的所有营地。在随后的探险中,如果记录包含了新的营地,那么这些营地被视为新发现。我们需要确定在哪次探险中,小扣发现了最多的新营地,并返回那次探险的索引。如果所有探险都没有发现新营地,我们则返回-1。

在这个问题中,每个营地都是独一无二的,即使是大小写不同也被视为不同的营地。每个营地的名称都是非空的,保证了至少存在一个字符。

此问题的挑战在于如何有效地解析这些记录,并准确地识别出新发现的营地。

原始解决方案分析

为了找出小扣在哪次探险中发现了最多的新营地,我们需要对探险记录进行分析。原始的解决方案可能是按照以下步骤进行:

  • 初始化一个集合originCamp,用于存储第一次探险已知的所有营地。
  • 遍历expeditions数组,从第二个元素开始,因为第一个元素代表的是已知营地。
  • 对于每个后续的探险记录,使用字符串分割操作,将营地名称分割开来。
  • 遍历分割后得到的营地名称数组,检查每个名称是否在originCamp集合中。
  • 如果发现了不在集合中的新营地,将其添加到集合中,并更新本次探险的新发现营地计数。
  • 记录新发现营地数量最多的探险索引和对应的新营地数量。

完成遍历后,返回新发现营地数量最多的探险索引,如果没有新发现的营地,则返回-1。

这个方案的主要问题在于,它需要执行大量的字符串分割操作和集合成员检查,这在探险记录较多或营地名称较长时,会导致效率低下。

参考代码如下:

Java 复制代码
class Solution {
    public int adventureCamp(String[] expeditions) {
        // 对于起点的地点建立HashSet
        Set<String> originCamp = new HashSet<>();
        if (expeditions[0].length() > 0) {
            String[] camps = expeditions[0].trim().split("->");
            for (String camp : camps) {
                originCamp.add(camp);
            }
        }

        // 记录结果
        int res = -1;
        int mx = 0;
        for (int i = 1; i < expeditions.length; i++) {
            if (expeditions[i].length() == 0) {
                continue;
            }
            String[] camps = expeditions[i].trim().split("->");
            int hasCamp = 0;
            for (String camp : camps) {
                // 若既不在起始营地,也不在访问过的营地
                boolean originFlag = originCamp.contains(camp);
                if (!originFlag) {
                    hasCamp++;
                    originCamp.add(camp);
                }
            }
            if (hasCamp > mx) {
                res = i;
                mx = hasCamp;
            }
        }
        return res;
    }
}

优化方案:哈希值

在处理"探险营地"问题的原始解决方案中,我们直接操作字符串来识别新发现的营地。这种方法在数据量较小的情况下是可行的,但随着数据量的增加,字符串比较的开销也随之增大,这直接影响了算法的性能。为了提高效率,我们将解决方案从直接处理字符串转变为处理哈希值。

哈希值处理的优势

处理哈希值相比于处理完整的字符串有两个主要优势:

  • 性能提升:哈希值通常为整数,整数之间的比较远比长字符串之间的比较更加高效。
  • 空间节省:在存储和操作时,整数占用的空间远小于长字符串。

应对哈希冲突

然而,直接使用标准的hashCode方法可能会引发哈希冲突,即不同的字符串可能产生相同的哈希值。以Java默认的hashCode算法为例。我们给出代码:

Java 复制代码
public int hashCode() { 
    int h = hash; // 默认为0 
    if (h == 0 && value.length > 0) { 
        char val[] = value; // 字符串的字符数组 
        for (int i = 0; i < value.length; i++) { 
            h = 31 * h + val[i]; 
        } 
        hash = h; 
    } 
    return h; 
}

在本题中,采用默认的hashCode方法会导致hash冲突。为了解决这个问题,我们采用了自定义哈希算法。

应用自定义哈希算法

我们将采用以下步骤以应用自定义哈希算法:

  • 设计一个自定义哈希函数,该函数能够根据营地名称生成唯一的哈希值。
  • 创建一个哈希表来存储营地名称与其哈希值的映射。
  • 在处理探险记录时,首先通过自定义哈希函数计算每个营地名称的哈希值。
  • 使用计算出的哈希值来更新已知营地集合,并统计新发现的营地数量。
  • 根据新发现的营地数量更新记录,并维护最多新营地发现的探险索引。
  • 遍历完成后,返回新营地发现最多的探险索引,若无新发现,则返回-1。

改进后的代码如下:

Java 复制代码
class Solution {

    // 获取字符串的哈希值,以'->'分割
    private List<Long> getStringHshs(String str) {
        int i = 0;
        long hsh = 0;
        char[] chs = str.toCharArray();
        List<Long> hshs = new ArrayList<>();
        if (chs.length > 0) {
            for (; i < chs.length; ) {
                if (chs[i] == '-') {
                    i += 2;
                    hshs.add(hsh);
                    hsh = 0;
                } else {
                    hsh = hsh * 199 + chs[i];
                    i++;
                }
            }
        }
        if (hsh != 0) {
            hshs.add(hsh);
        }
        return hshs;
    }


    public int adventureCamp(String[] expeditions) {
        // 对于起点的地点建立HashSet
        Set<Long> originCamp = new HashSet<>();
        if (expeditions[0].trim().length() > 0) {
            List<Long> hshs = getStringHshs(expeditions[0]);
            for (Long hsh : hshs) {
                originCamp.add(hsh);
            }
        }
        // 记录结果
        int res = -1;
        int mx = 0;
        for (int i = 1; i < expeditions.length; i++) {
            if (expeditions[i].length() == 0) {
                continue;
            }
            List<Long> hshs = getStringHshs(expeditions[i]);
            System.out.println();
            int hasCamp = 0;
            for (Long hsh : hshs) {
                // 若既不在起始营地,也不在访问过的营地
                boolean originFlag = originCamp.contains(hsh);
                if (!originFlag) {
                    hasCamp++;
                    originCamp.add(hsh);
                }
            }
            if (hasCamp > mx) {
                res = i;
                mx = hasCamp;
            }
        }
        return res;
    }
}

总结

通过引入自定义哈希算法,在处理时间上有了显著的变化。

原始方案处理探险记录的时间为187毫秒,而经过优化后,时间缩短至96毫秒。这一改进减少了近一半的执行时间,证明了优化的有效性。

相关推荐
HUT_Tyne2651 小时前
力扣--LCR 154.复杂链表的复制
java·leetcode·链表
一只鸡某2 小时前
实习冲刺第三十一天
数据结构·c++·算法·leetcode·排序算法
Y.O.U..3 小时前
力扣刷题-excel表名称序列相转换
算法·leetcode·excel
Lenyiin4 小时前
02.02、返回倒数第 k 个节点
c++·算法·leetcode
Wils0nEdwards5 小时前
Leetcode 组合
leetcode
A Runner for leave6 小时前
105.找到冠军
java·数据结构·python·算法·leetcode
硕风和炜8 小时前
【LeetCode: 743. 网络延迟时间 + Dijkstra】
java·算法·leetcode·面试·dijkstra·最短路径
搞笑症患者9 小时前
LeetCode Hot100 - 矩阵篇
算法·leetcode·矩阵
冠位观测者9 小时前
【Leetcode Top 100】240. 搜索二维矩阵 II
数据结构·算法·leetcode
TANGLONG22210 小时前
【初阶数据结构和算法】leetcode刷题之设计循环队列
java·c语言·数据结构·c++·python·算法·leetcode