Leetcode第一題:兩數之和(3種語言)
Leetcode第一題:兩數之和
給定一個整數陣列 nums 和一個目標值 target,請你在該陣列中找出和為目標值的 兩個 整數。
你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個陣列中同樣的元素。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
標註:僅在Python解法做詳細分析,c++與java如無特別需要注意的地方不做分析。
一、Python解法
解法2,3參考了linfeng886的部落格
解法1:暴力搜尋
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
#對nums每個元素迴圈
for i in range(len(nums)):
#從nums[i]的下一個開始找
for j in range(i+1,len(nums)):
#如果找到了就返回值
if nums[i]+nums[j]==target:
return i,j
分析:程式碼十分簡單,就是首先用i在陣列裡迴圈一輪,在每個i迴圈下,去從剩下的元素找target-nums[i]的值。如果找到了,就return i,j兩個數。(程式假設一定可以找的到)
來看看執行結果:
可以看到效率是十分低的,主要原因就是用了兩個for迴圈,時間複雜度是O(n2)。
解法2:一次for迴圈
一開始犯了一個小錯誤的程式碼
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
#直接在i一個一個迴圈的時候,直接判斷target-nums[i]在列表裡嗎,在的話用list.index()獲取索引,用了一次for迴圈。很棒。
for i in range(len(nums)):
if target-nums[i] in nums:
return i,nums.index(target-nums[i])
此程式碼的執行問題在於:
我們可以看到,忘記了一個元素只能用一次的規矩,因此做一個判斷即可。
修改版:
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
for i in range(len(nums)):
if target-nums[i] in nums:
#增加了返回的兩個數下表不能相同的判斷
if i!=nums.index(target-nums[i]):
return i,nums.index(target-nums[i])
可以看到:速度上升了好幾倍。效果還不錯。但是通過檢視官方解答參考了linfeng886的部落格知道還有一種更快的方法----基於hash table的解決方法。
解法3:基於Python字典查詢
關於hash table(雜湊表),簡單來說就是存有鍵值對key,value的一種資料結構,對於熟悉Python的人來說,常見的字典就是一種hash table。它的查詢速度是很快的,可以理解為O(1)。所以這裡相當於在解法2的基礎上做了一個改進。解法2 是在空間不變的前提下,在i迴圈時,直接在列表裡查詢是否含有target-nums[0]的元素,而列表的查詢速度是遠不如hash table。所以解法三的關鍵就在於,查詢的任務在字典中進行而不在list中進行(有待商榷)
程式碼:
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
#遍歷一個i,就把nums[i]放在字典裡。之後i不斷相加,總會碰到
#target - nums[i]在字典裡的情況。碰到的時候return即可。這裡注意到先return的是dict[a],再是當前的i,原因就是dict裡存的value都是以前遍歷過的i,所以它比當前的i小,按從小到大的順序所以這樣排。
dict = {}
for i in range(len(nums)):
a = target - nums[i]
if a in dict:
return dict[a],i
else:
dict[nums[i]] = i
執行結果:
可以從程式碼中看到,解法3就是把需要查詢的target-nums[i]從解法2中list變成了dict而已,但結果提高的十分可觀。但要注意的是,這裡用空間與實踐做了一個tradeoff,也就說解法3雖然快了很多,但是需要的空間增加了一個新的dict。
這也給我們了一個提示:以後如遇到類似的需要遍歷查詢的元素時,不妨參照本例,利用hash table查詢。
二、Java解法
解法2,3參照了官方解法twosum
以及菜鳥教程java陣列篇
Pythonliast與java陣列區別 https://blog.csdn.net/wu1226419614/article/details/80870120
關於hashmap資料型別
https://www.cnblogs.com/hello-yz/p/3712610.html
解法1:暴力搜尋
程式碼:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] a = new int[2];
for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length;j++){
if(nums[i]+nums[j]==target){
a[0] = i;
a[1] = j;
//return new int[] {i,j};
}
}
}
return a;
}
}
這裡不作特別說明。只想提及的是關於return new int[] {i,j}的些許解釋。這種寫法是官方解讀給出的。參考菜鳥教程關於陣列的解釋,可以知道這是函式輸入引數或者充當返回值的一種方式。
同時,官方解法在類的最後會throw一個異常,若將其刪除會報錯,因為throw也是return的一種替代形式。(就是說即使這個類在開頭就說了不是void的,要返回一個int[]或者其他的東西,但是在最後丟擲一個異常語法上是符合的。)對於本例,執行著就會從if下的return離開程式,所以不會丟擲異常的。
解法2:兩次for迴圈(兩遍hashmap)
在這裡和Python的3種解法做一個比較。可以看到兩種語言的解法1是完全相同的。但是解法2上,會有一些區別。之後解法3又是完全相同的。為什麼解法2會和Python解法2有區別呢?
先回顧下Python解法2:通過i迴圈列表,直接判斷target - nums[i]是否在列表裡,在的話,就直接返回i,與list.index(target-nums[I])。這裡我們用了Python內建函式index。可以方便的獲取到索引,而對於java的陣列,並沒有那麼方便獲取陣列元素索引的函式。這裡有一個很好的比較,從中可以知道java對於陣列有一個binarySearch的查詢方法,而它本身就是用二分法查詢實現的,所以只適用於有序陣列。同時若再用一次for迴圈獲取索引,得不償失。那不如多用一次for迴圈把索引與數值一一對應起來,用類似Python字典的方式,這樣查詢的更快-----hashmap
程式碼:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] b = new int[2];
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
map.put(nums[i],i);
}
for(int j=0;j<nums.length;j++){
int a = target - nums[j];
if(map.containsKey(a) && j!=map.get(a)){
b[0] = j;
b[1] = map.get(a);
return b;
//return new int[] {j,map.get(a)};
}
}
return b;
}
}
這裡需要注意幾點:
1.HashMap<Integer,Integer> = new HshMap<Integer,Integer>()這裡的Integer不能用基本資料型別int。可以參看這裡。
2.hashmap宣告定義格式,和一般類的例項化一樣。
class1 xx = new class1()
3.hashmap與hashtable。hashmap基本上可以等同於hashtable。而且可以看作為其升級版。HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。詳情可檢視 http://www.importnew.com/7010.html 。
4.hashmap的put,get分別為存與取。以及containsKey在程式碼裡都有體現,很容易理解。
5.注意if判斷裡的&&(短路運算子)不能被&代替,可以通過LeetCode的簡單試例,但是提交時會報錯。分析:&即使前面為false,運算子後面的邏輯式還是會被判斷。而後面的為j != map.get(a)。很可能a是不存在的,故會報錯。將map.get(a)列印出來是一個null指標。進一步解釋,執行用&的程式碼會報錯:
Exception in thread “main” java.lang.NullPointerException
這也就是空指標異常。解釋是"程式遇上了空指標"。簡單地說就是呼叫了未經初始化的物件或者是不存在的物件,這個錯誤經常出現在建立圖片,呼叫陣列這些操作中,比如圖片未經初始化,或者圖片建立時的路徑錯誤等等。對陣列操作中出現空指標。陣列的初始化是對陣列分配需要的空間,而初始化後的陣列,其中的元素並沒有例項化,依然是空的,所以還需要對每個元素都進行初始化(如果要呼叫的話)。參見 https://zhidao.baidu.com/question/494551043.html
但是,這一切在python中是允許的。
解法3:一次for迴圈(一次hashmap)
此解法思想與Python解法3如出一轍,是一模一樣的,唯一區別在於Python使用字典做查詢,Java使用HashMap做查詢。因此本解法不做過多說明。
程式碼:
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
return new int[] {map.get(nums[i]),i};
}
map.put(target-nums[i],i);
}
return new int[]{1,1,1};
}
}
可以看到,與解法2相比速度提升有限。
這裡需要注意的是:1.程式碼最後一行無論return的是什麼(必須是陣列)無所謂的,因為不會走到這一步的,但是最優解還是像官方解答一樣丟擲一個異常比較好。因為如果真走到這一步了,說明程式肯定出現了異常。
2.我這裡寫的和官方解法略有不同,其實是一樣的。我把put進去的值換成了target-nums[i]。然後判斷nums[i]是否在map中,具體就不說了,略顯繁瑣。
三、C++解法
有了上述兩種語言的解答做鋪墊,我覺得C++解法思路就會清晰很多了。
解法1:暴力搜尋
直接看程式碼:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//vector型別為長度可變的陣列,更靈活。需要#include<vector>
vector<int> a;
//int a[2];這裡指定返回的是verctor型別,故這裡不能用普通陣列array
for(int i=0;i<nums.size();i++){
for(int j =i+1;j<nums.size();j++){
if(nums[i]+nums[j]==target){
//在a的最後新增元素
a.push_back(i);
a.push_back(j);
//a[0] = i;
//a[1] =j;
return a;
}
}
}
//注意這裡和java不一樣,不需要一定要返回一個vector了。雖然該類要求有一個返回值
}
};
這裡做一些筆記:
1.求陣列長度。
Python:len(list);
java:nums.length;
c++:nums.size()
2.java與c++的基本陣列型別長度都是不可變的,要求靈活的使用用vector代替。關於陣列與vector在c++與Java中的使用
c++相關參見菜鳥教程
Java的vector參見zheting的部落格
重要的一點貼出來了:
java使用需要import java.util.Vector;
插入功能:
public final synchronized void adddElement(Object obj)
將obj插入向量的尾部。obj可以是任何型別的物件。對同一個向量物件,亦可以在其中插入不同類的物件。但插入的應是物件而不是數值,所以插入數值時要注意將陣列轉換成相應的物件。
例如:要插入整數1時,不要直接呼叫v1.addElement(1),正確的方法為:
Vector v1 = new Vector();
Integer integer1 = new Integer(1);
v1.addElement(integer1);
3.關於陣列作為函式的形參。
本例中是這樣寫的:
vector<int> twoSum(vector<int>& nums, int target) {
//insert your code
}
或者這樣寫:
vector<int> twoSum(vector<int> &nums, int target) {
//insert your code
}
或者:
vector<int> twoSum(vector<int> nums, int target) {
//insert your code
}
具體可檢視菜鳥教程關於陣列做形參的講解
解法2:兩次map(非雜湊表)
這裡注意的是用的是c++的map實現的key,value配對。而c++中還有hash_map,即hash table。二者的區別是:
hash_map採用hash表儲存,map一般採用紅黑樹(RB Tree)實現。因此其記憶體資料結構是不一樣的。
二者區別具體可檢視
zhenyusoso的部落格與Miles-的部落格。
先來看map實現的程式碼:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> a;
map<int,int> map;
//hash_map<int,int> hp;
for(int i=0;i<nums.size();i++){
map[nums[i]] = i;
}
for(int j=0;j<nums.size();j++){
if(map.count(target-nums[j])==1 && map[target-nums[j]]!=j){
a.push_back(j);
a.push_back(map[target-nums[j]]);
return a;
}
}
return a;
}
};
做幾點分析:
1.此方法和java的解法2版本是十分接近的。主要不同之處在於java的hashmap呼叫的方法有:put(key,value),get(key),containsKey()
而c++的map查詢鍵值是否在為count。關於map的count與find是很常用的方法,二者用途一樣。具體見Andy Niu的部落格。
2.這裡用的是map,為什麼不用hash_map類來實現呢?我在xcode中實現了一遍,macos系統關於hash_map的宣告比較特殊:
#if defined __GNUC__ || defined __APPLE__
#include <ext/hash_map>
#else
#include <hash_map>
#endif
int main()
{
using namespace __gnu_cxx;
hash_map<int, int> map;
}
同時用find方法判別hash_map是否含有想要的key。
hash_map<int,int> hp;
if(hp.find(target-nums[j])!=hp.end() && hp[target-nums[j]]!=j){
//程式碼區
}
解法3:1次map(非雜湊表)
這個就沒有什麼要分析的了,和上面兩種語言的解法三一模一樣。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> a;
map<int,int> map;
for(int i=0;i<nums.size();i++){
if(map.find(target-nums[i])!=map.end()){
a.push_back(map[target-nums[i]]);
a.push_back(i);
return a;
}
else{
map[nums[i]] = i;
}
}
return a;
}
};
這裡程式碼用了map的find而不是count來判斷map是否含有指定key。
ps:後面的刷題筆記應該會3種語言分開記錄,這樣一篇文章太長,容易產生厭倦感。
相關文章
- leetcode 解題 1.兩數之和-python3 兩種解法 @ 官方LeetCodePython
- LeetCode 之 JavaScript 解答第一題 —— 兩數之和(Two Sum)LeetCodeJavaScript
- LeetCode - 兩數之和LeetCode
- LeetCode:兩數之和LeetCode
- [LeetCode 刷題] 1. 兩數之和LeetCode
- LeetCode題集-1- 兩數之和LeetCode
- leetcode #1 兩數之和LeetCode
- LeetCode 1 兩數之和LeetCode
- LeetCode之兩數之和LeetCode
- LeetCode-兩數之和LeetCode
- LeetCode每日一題:兩數之和(No.1)LeetCode每日一題
- #leetcode刷題之路1-兩數之和LeetCode
- leetCode解題記錄1 - 兩數之和LeetCode
- LeetCode: Two sum(兩數之和)LeetCode
- LeetCode-1. 兩數之和LeetCode
- leetcode-0001 兩數之和LeetCode
- LeetCode 1. 兩數之和LeetCode
- LeetCode每日一題 (32)1. 兩數之和LeetCode每日一題
- 用python手刃Leetcode(1):兩數之和【簡單題】PythonLeetCode
- 2020/10/31·Leetcode·兩數之和LeetCode
- LeetCode 1 兩數之和(簡單)LeetCode
- 組隊刷LeetCode - 兩數之和LeetCode
- 力扣演算法經典第一題——兩數之和(Java兩種方式實現)力扣演算法Java
- 簡單演算法題:leetcode-1 兩數之和演算法LeetCode
- python leetcode 之兩數之和(two sum)PythonLeetCode
- LeetCode-Python 1. 兩數之和LeetCodePython
- 2020-10-12 Leetcode 兩數之和LeetCode
- 【leetcode 簡單】 第八十七題 兩整數之和LeetCode
- [演算法] LeetCode 1.兩數之和演算法LeetCode
- 每日一道 LeetCode (1):兩數之和LeetCode
- Leetcode力扣1 兩數之和(Python版)LeetCode力扣Python
- leetcode演算法熱題--兩樹之和LeetCode演算法
- Go語言效能優化-兩數之和演算法效能研究Go優化演算法
- Fifth. LeetCode 2:Add Two Numbers 兩數之和LeetCode
- LeetCode 每日一題,用 Go 實現兩數之和的非暴力解法LeetCode每日一題Go
- 兩數之和
- LeetCode 演算法 | 兩數之和不簡單啊LeetCode演算法
- 兩數之和,三數之和,最接近的三數之和,四數之和