leecode-15 三数之和

在这里插入图片描述

我的解法(不是完全解309/314)

  • 我的思路是定义一个left和一个right,然后在向集合里去查询,看看有没有除了nums[left],和nums[right]的第三个元素,把这个问题转换为一个遍历查找问题

    • 利用List.contains()方法来查找剩余元素
    • 利用List.remove和List.add(),来模拟派出了nums[left]和nums[right]的集合
  • 但是这种思路有个问题,就是时间复杂度太高了,我只通过了309/314的示例。

    • 其中这个ArrayList.contains 是 O(n),ArrayList.remove(Integer) 也是 O(n),然后外层有一个从left-length,有一个从length-left,循环接近O(n3),时间复杂度太高了
    • int left = 0,right =0;
      ArrayList<Integer> list = new ArrayList<>();
      for(int num:nums){
          list.add(num);
      }
      List<List<Integer>> result = new ArrayList<>();
      for(;left<nums.length-2;left++){
          list.remove(Integer.valueOf(nums[left]));
          List<Integer> temp = new ArrayList<>();
          int a =0;
          for(right=nums.length-1;right>left;right--){
              list.remove(Integer.valueOf(nums[right]));
              a = 0-nums[left]-nums[right];
              if(list.contains(a)){
                  Collections.addAll(temp,nums[left],nums[right],a);
                  List<Integer> temp2 = new ArrayList<>(temp);
                  Collections.sort(temp2);
                  if(!result.contains(temp2)){
                      result.add(temp2);
                  }
                  temp.clear();
              }
              list.add(nums[right]);
          }
      }
      return result;
      
  • 改进1:对于这个代码我们需要想办法减少它的时间复杂度,为了遍历出所有情况,内外两层循环遍历是不能够改变的。所以我们可以想办法去减少这个查找的时间复杂度,将这个集合转换为哈希集合,这样查询的时间复杂度就变成了O(1)了

    • 但是很遗憾,这个时间复杂度还是高了,还需要继续降时间复杂度
    • List<List<Integer>> result = new ArrayList<>();
      // 用 HashMap 统计每个数字的出现次数(替代 ArrayList)
      Map<Integer, Integer> count = new HashMap<>();
      for (int num : nums) {
          count.put(num, count.getOrDefault(num, 0) + 1);
      }
      // 为了去重,排序后按顺序枚举
      int n = nums.length;
      for (int i = 0; i < n - 2; i++) {
          int left = nums[i];
          count.put(left, count.get(left) - 1); // 用掉一个 left
          for (int j = n-1; j >i; j--) {
              int right = nums[j];
              count.put(right, count.get(right) - 1); // 用掉一个 right
              int target = 0 - left - right;
              // 检查剩余集合中是否有 target
              if (count.getOrDefault(target, 0) > 0) {
                  List<Integer> list = Arrays.asList(left, right, target);
                  Collections.sort(list); // 排序以确保结果的唯一性
                  if (!result.contains(list)) {
                      result.add(list);
                  }
              }
              // 把 right 加回来,供下一轮使用
              count.put(right, count.get(right) + 1);
          }
      }
      return result;
      

灵神的思路

  • 灵神在讲这个题的时候先参考了这个 📄((20250730131645-jdjxatg ‘0167 两数之和II-输入有序数组’)) 这个题
    如果是两个数的话,我们就可以使用两个指针进行快速的寻找
    所以我们想想办法,看怎么使这个三个数的和变成两个数

  • 我们可以通过先对这个数组进行排序,这样就和0167题具有了相同的初始条件,即数组有序
    之后我们对这个数组进行遍历,先确定一个数,之后再剩余的数组中,找到两个数的和为0-这个数,此时就和0167题相似了。

  • 有一个点,不太容易理解,就是为什么left是从i+1开始,而不是从别的地方开始
    是因为,每次遍历的时候,都会把这个位置的所有情况给考虑到,后续的其它集合就不会包含这个元素

    //讲数组进行排序
    Arrays.sort(nums);
    int n = nums.length;
    List<List<Integer>> ans = new ArrayList<>();
    for( int i= 0; i<n-2;i++){
        if(i>0&&nums[i]==nums[i-1]){
            continue;//代表这个重复了
        }
        int left = i+1;
        int right= n-1;
        int target = 0 - nums[i];
        while(left<right){
            if(nums[left]+nums[right]>target){
                right--;
            }else if(nums[left]+nums[right]<target){
                left++;
            }else {
                ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
                //之后的话进行下一轮,因为可能还有重复,我们将left和right修改
                //比如[-2,-2,-2,-2,4,4,4,4,4],这个地方就有可能重复
                left++;
                while(left<right&&nums[left]==nums[left-1]){
                    left++;
                }
                right--;
                while(left<right&&nums[right]==nums[right+1]) {
                    right--;
                }
            }
        }
    }
    return ans;
    
  • 如何剪枝
    当我们确定了第一个数,发现它和这个排序后的数组中最大的两个数相加都小于0的话,那么这个都不用看了,不可能会有等于0的情况,我们此时切换到下一个数
    当我们确定了第一个数,如果它和它紧挨着的两个数(靠近它最小的数)相加大于0,那么后续都会大于0,直接跳过

  • if(nums[i]+nums[i+1]+nums[i+2]>0){
        break;
    }
    if(nums[i]+nums[n-1]+nums[n-2]<0){
        continue;
    }
    
【四轴飞行器】非线性三自由度四轴飞行器模拟器研究(Matlab代码实现)内容概要:本文围绕非线性三自由度四轴飞行器的建模与仿真展开,重点介绍了基于Matlab的飞行器动力学模型构建与控制系统设计方法。通过对四轴飞行器非线性运动方程的推导,建立其在三维空间中的姿态与位置动态模型,并采用数值仿真手段实现飞行器在复杂环境下的行为模拟。文中详细阐述了系统状态方程的构建、控制输入设计以及仿真参数设置,并结合具体代码实现展示了如何对飞行器进行稳定控制与轨迹跟踪。此外,文章还提到了多种优化与控制策略的应用背景,如模型预测控制、PID控制等,突出了Matlab工具在无人机系统仿真中的强大功能。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的高校学生、科研人员及从事无人机系统开发的工程师;尤其适合从事飞行器建模、控制算法研究及相关领域研究的专业人士。; 使用场景及目标:①用于四轴飞行器非线性动力学建模的教学与科研实践;②为无人机控制系统设计(如姿态控制、轨迹跟踪)提供仿真验证平台;③支持高级控制算法(如MPC、LQR、PID)的研究与对比分析; 阅读建议:建议读者结合文中提到的Matlab代码与仿真模型,动手实践飞行器建模与控制流程,重点关注动力学方程的实现与控制器参数调优,同时可拓展至多自由度或复杂环境下的飞行仿真研究。
<think>首先,用户的问题是:“我想使用Java解决LeetCode算法题中的两数之和问题 请问LeetCode Java实现 两数之和问题解决方案”。这直接指向LeetCode的第一题“Two Sum”。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户可见层要求回答结构清晰,帮助用户逐步解决问题。 参考的引用是关于LeetCode两数之和问题的Java实现: - 引用[1]:描述问题背景。 - 引用[2]:暴力解法代码。 - 引用[3]:暴力解法代码。 - 引用[4]:暴力解法代码。 - 引用[5]:暴力解法代码。 所有引用都展示了暴力解法,即双重循环。但我知道两数之和问题有更高效的解法,比如使用哈希表。我应该提供最优解。 标准解决方案: - 暴力法:时间复杂度$O(n^2)$,空间复杂度$O(1)$。 - 哈希表法:时间复杂度$O(n)$,空间复杂度$O(n)$。 用户要求Java实现,所以我需要提供代码。 回答结构: 1. **介绍问题**:简要描述LeetCode两数之和问题。 2. **解决方案**: - 解释暴力解法(因为引用中提到,可以作为基础)。 - 解释优化解法(哈希表法)。 - 提供Java代码。 3. **逐步解释**:确保清晰,帮助用户理解。 4. **生成相关问题**:在末尾添加相关问题。 5. **引用标识**:如果引用站内内容,在末尾添加引用标识。引用中提到了暴力解法,所以我可以在提到暴力解法时添加引用。 引用内容: - 引用[2][3][4][5]都提供了暴力解法的代码。我可以参考这些,但需要自然添加引用标识。 例如,在描述暴力解法时,可以说“如引用[2]所示”。 现在,构建回答大纲: - **开头**:介绍问题。 - **暴力解法**:解释和代码,添加引用标识。 - **优化解法**:哈希表法,解释和代码。 - **比较**:简要比较两种方法。 - **结尾**:总结。 - **相关问题**:生成相关问题。 使用中文回答。 数学表达式:如果涉及数学,如时间复杂度,使用$...$。 例如: - 时间复杂度:$O(n^2)$ 或 $O(n)$。 独立公式:可能不需要,因为主要是代码。 现在,写回答。</think>### LeetCode 两数之和问题 Java 解决方案 您好!LeetCode 的“两数之和”问题(题目编号:1)是一个经典算法题。给定一个整数数组 `nums` 和一个目标值 `target`,要求找出数组中两个数的索引,使得它们的和等于 `target`。假设每个输入只有一个解,且不能重复使用同一元素。我将逐步解释两种 Java 实现方法:暴力解法和优化解法(哈希表法)。所有代码都基于 LeetCode 官方题解和常见实践,确保真实可靠。我会先介绍问题背景,然后提供代码和详细解释。 #### 问题描述 给定一个数组 `nums` 和目标值 `target`,找出两个索引 `i` 和 `j`($i \neq j$),使得 $nums[i] + nums[j] = target$。例如,输入 `nums = [2, 7, 11, 15]`, `target = 9`,输出应为 `[0, 1]`(因为 $2 + 7 = 9$)。 #### 解决方案 1: 暴力解法(双重循环) 这是最直观的方法:遍历数组中的每个元素,对于每个元素 `nums[i]`,再遍历其后的元素 `nums[j]`($j > i$),检查是否满足 $nums[i] + nums[j] = target$。如果找到,立即返回索引对。 - **时间复杂度**:$O(n^2)$,其中 $n$ 是数组长度。因为需要两层循环。 - **空间复杂度**:$O(1)$,不使用额外空间。 - **优点**:实现简单,适合小规模数组。 - **缺点**:效率低,当 $n$ 较大时性能差。 以下是 Java 代码实现(基于引用[2][3][5]的暴力解法): ```java public class Solution { public int[] twoSum(int[] nums, int target) { // 遍历每个元素 for (int i = 0; i < nums.length - 1; i++) { // 对于当前元素 nums[i],检查其后所有元素 for (int j = i + 1; j < nums.length; j++) { // 如果找到满足条件的元素对,返回索引 if (nums[i] + nums[j] == target) { return new int[]{i, j}; } } } // 如果无解,返回空数组(题目保证有解,但这里做安全处理) return new int[0]; } } ``` **代码解释**: - 外层循环从索引 `i = 0` 开始,遍历到 `nums.length - 1`(因为最后一个元素无需作为起始点)。 - 内层循环从 `j = i + 1` 开始,避免重复比较(如引用[3]所述,这样可以减少不必要的计算)。 - 当发现 `nums[i] + nums[j] == target` 时,直接返回 `new int[]{i, j}`。 - 如果遍历完无解,返回空数组 `new int[0]`(尽管题目保证有解,但代码需健壮性)。 此方法简单易懂,但效率不高,适合初学者理解问题本质[^2][^3][^5]。 #### 解决方案 2: 优化解法(哈希表法) 为了提高效率,可以使用哈希表(HashMap)存储元素值及其索引。遍历数组时,检查 `target - nums[i]` 是否已存在于哈希表中。如果存在,说明找到了解;否则,将当前元素存入哈希表。这避免了嵌套循环。 - **时间复杂度**:$O(n)$,只需一次遍历。 - **空间复杂度**:$O(n)$,哈希表最多存储 $n$ 个元素。 - **优点**:高效,适合大规模数组。 - **缺点**:使用额外空间,但通常可接受。 以下是 Java 代码实现(基于 LeetCode 官方推荐): ```java import java.util.HashMap; import java.util.Map; public class Solution { public int[] twoSum(int[] nums, int target) { // 创建哈希表,键为元素值,值为索引 Map<Integer, Integer> numMap = new HashMap<>(); // 遍历数组 for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; // 计算补数 // 检查补数是否在哈希表中 if (numMap.containsKey(complement)) { // 如果存在,返回当前索引和补数的索引 return new int[]{numMap.get(complement), i}; } // 将当前元素存入哈希表(值作为键,索引作为值) numMap.put(nums[i], i); } // 无解时返回空数组 return new int[0]; } } ``` **代码解释**: - 初始化一个 `HashMap`,键(key)存储元素值,值(value)存储对应索引。 - 遍历数组:对于每个 `nums[i]`,计算 `complement = target - nums[i]`。 - 检查 `complement` 是否在哈希表中:如果存在,说明之前已存储了该值,直接返回 `numMap.get(complement)`(补数的索引)和 `i`(当前索引)。 - 如果未找到,将 `nums[i]` 和 `i` 存入哈希表。 - 这种方法只需一次遍历,效率远高于暴力解法。 #### 方法比较 - **暴力解法**:简单但慢,适用于学习或小数据集($n \leq 1000$)。 - **哈希表法**:推荐解法,速度快,适用于大数据集($n > 1000$)。在实际 LeetCode 提交中,哈希表法通常运行时间在 2-5ms,而暴力解法可能超时(如 $n=10^4$ 时)。 #### 测试示例 在 LeetCode 上测试时,可以使用以下输入验证: - 输入:`nums = [2,7,11,15]`, `target = 9` → 输出:`[0,1]` - 输入:`nums = [3,2,4]`, `target = 6` → 输出:`[1,2]` 确保代码处理边界情况,如数组为空或无解(尽管题目保证有解)。 #### 总结 通过以上两种方法,您可以高效解决 LeetCode 两数之和问题。暴力解法帮助理解基础,哈希表法是实际最优解。建议在 LeetCode 平台上提交代码测试性能。如果您是初学者,先从暴力解法入手,再过渡到哈希表法,以加深对数据结构的理解[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值