開始在LeetCode上刷題(1-9)
因為兩週前,對繼續讀理論書有點抗拒,所以想找點可以直接動手做的事,後來就找到了LeetCode。
到目前在LeetCode上完成了前9道題。
比較考腦子,就算把功能完成了,但隨後LeetCode顯示的排名也會讓你想繼續想如何優化已經寫好的程式碼,往往優化的結果就是把程式碼又重新實現了一次,比較費時間,但還好比較有意思。
LeetCode的題有些我是用JAVA實現,有些是用C#實現的。
1.Two Sum
這道題我最開始的實現很簡單,直接兩個for迴圈加一個判斷就能實現了。
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++)
{
//// find two numbers such that they ....
for (int j = i + 1; j < nums.length; j++)
{
if (nums[i] + nums[j] == target)
{
if (i < j)
{
/// / your returned answers (both index1 and index2) are not zero-based.
return new int[] { i + 1, j + 1 };
}
else
{
return new int[] { j + 1, i + 1 };
}
}
}
}
/// / targe not found
return new int[] { 0, 0 };
}
一次跑通,但最後的跑分結果:
所以換了一個實現方式。把兩個for迴圈的遍歷換成了字典儲存nums[]的所有成員。
我們要做的是每次在字典裡查詢是否主鍵為target-nums[i]。
import java.util.Hashtable;
import java.util.Map;
public class Solution {
private Map<Integer, Integer> hashTable = new Hashtable<Integer, Integer>();
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length; i++)
{
hashTable.put(nums[i], i);
}
Integer index = new Integer(0);
int indexInt = 0;
for(int i = 0; i < nums.length; i++)
{
index = hashTable.get(new Integer(target - nums[i]));
if(index != null && (int)index != i)
{
indexInt = (int)index;
if(i > indexInt)
{
i ^= indexInt;
indexInt ^= i;
i ^= indexInt;
}
return new int[]{i + 1, indexInt + 1};
}
}
return new int[]{0, 0};
}
}
這樣實現後跑分為40%,但還是不夠,後面再改進吧。
2.Add Two Numbers
這道題一開始我就審錯題了。
由此帶來的結果是寫完的程式碼對不上題目想要的邏輯。
又出現短節了。
事後好好想了下思維上出現的問題。
當A,B是遵循了The digits are stored in reverse orde。那麼結果也該遵循這個規則。同一種類型應該是遵循一種規則的,為什麼我會把AB和C分割開來思考,他們本該是一條線的,是不是我又想當然了,思維又開始到處跳躍了。
這道題需要處理的是進位。
跑分90%。
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int x) { val = x; }
* }
*/
public class Solution {
public ListNode AddTwoNumbers(ListNode l1, ListNode l2) {
return this.Calc(l1, l2, 0);
}
private ListNode Calc(ListNode l1, ListNode l2, int carry)
{
if (l1 == null && l2 == null)
{
return carry == 0 ? null : new ListNode(1);
}
if (l1 == null)
{
l1 = new ListNode(0);
}
if (l2 == null)
{
l2 = new ListNode(0);
}
int sum = l1.val + l2.val + carry;
ListNode head = new ListNode(sum % 10);
head.next = Calc(l1.next, l2.next, sum / 10);
return head;
}
}
3.Longest Substring Without Repeating Characters
這道題的解決思路類似前後兩個指標prve,next,最開始prve,next都指向字串第一個字元,然後每次next都移向下一個字元,而prev只在next所指向字元和prve指向字元相同時才後移,通過判斷next和prev之間的長度就可以得到最大未重複子串。
現在的問題是如何儲存上次字元出現的位置。思路類似第一題裡的字典,但這裡,我們可以用陣列來描述這個hash表,由於題目中可以出現的字元沒有做限制,那麼我們可以按照ASCII碼與每一個字元位置對應,作為陣列的索引,陣列中的內容就是出現這個字元出現的位置。
跑分90%。
public int lengthOfLongestSubstring(String s) {
//// 上次出現的位置
int[] exist = new int[256];
int index = 0;
int maxLength = 0;
char[] arr = s.toCharArray();
for(int i = 0; i < arr.length; i++)
{
if(exist[arr[i]] > index)
{
index = arr[i] + 1;
}
if(i - index > maxLength)
{
maxLength = i - index;
}
exist[arr[i]] = i;
}
//// 加上最後一次計算最大長度後再首次加入的字元數
return maxLength;
}
4.Median of Two Sorted Arrays
這道題的實現,我繞了幾個彎,要想辦法用上兩個子陣列都是已排序的條件,這樣的話用歸併排序很有優勢,但又要求是O(log(m+n))的時間複雜度,於是我用BST實現了整個排序,然後判斷奇偶求出中位數,但這樣的話我沒有用上兩個陣列已排序的前提條件,把兩個陣列都重新排了次序,這樣就浪費這個前提條件,查了下資料,很多實現都把這道題納入了求Kth值得範圍。如這裡求兩個有序陣列的第k大值。兩者本質上是一樣的。
對於其實現,雖然跑出了結果,但我感覺還是沒吃透。
跑分40%,還要改進。
private double findKth(int A[], int aBeg, int B[], int bBeg, int k)
{
if (A.length - aBeg < B.length - bBeg)
return findKth(B, bBeg, A, aBeg, k);
if (bBeg == B.length)
return A[aBeg + k - 1];
if (k == 1)
return Math.min(A[aBeg], B[bBeg]);
int pb = Math.min(k / 2, B.length - bBeg), pa = k - pb;
if (A[aBeg + pa - 1] > B[bBeg + pb - 1])
return findKth(A, aBeg, B, bBeg + pb, pa);
else if (A[aBeg + pa - 1] < B[bBeg + pb - 1])
return findKth(A, aBeg + pa, B, bBeg, pb);
else
return A[aBeg + pa - 1];
}
public double findMedianSortedArrays(int A[], int B[])
{
return (findKth(A, 0, B, 0, (A.length + B.length + 1) / 2) + findKth(A, 0, B, 0, (A.length + B.length + 2) / 2)) / 2.0;
}
5.Longest Palindromic Substring
這道題和第3題類似,只是那道題是找不重複,這裡是要找重複,思路是對於第i個字元,檢查i前一個和i後一個字元是否相等,相等則繼續擴張。
但這裡要區分abba和aba的情況。這樣的話一個指標就沒法解決這個問題了,那麼如果是兩個指標的話,i和j,比較i+1和j-1字元是否相等,這樣雖然還是會遇到只有一個指標時要遇到的情況,但卻不會錯過abba的情況了。
儲存是否兩個位置上的字元是否相等,仍可以用陣列來完成,第i個字元和第j個字元對於二維陣列ij。用布林型別來描述是否相等。
雖然實現了,但是最後的跑分,慘不忍睹。程式碼裡的打標記過程就花費了n^2的複雜度。
public String longestPalindrome(String s) {
char[] arr = s.toCharArray();
boolean[][] flag = new boolean[arr.length][arr.length];
int maxLength = 0;
int index = 0;
for(int i = 0; i < arr.length; i++)
{
for(int j = 0; j < arr.length; j++)
{
if(arr[i] == arr[j])
{
flag[i][j] = true;
}
}
}
for(int j = 1; j < arr.length; j++)
{
for(int i = 0; i < j; i++)
{
if(arr[i] == arr[j] && flag[i + 1][j - 1])
{
if(j - i > maxLength)
{
index = i;
maxLength = j - i;
}
}
else
{
flag[i][j] = false;
}
}
}
return s.substring(index, index + maxLength + 1);
}
6.ZigZag Conversion
這道題的目的是要你將字串的每個字元排列成鋸齒形狀再按每一行依次輸出。先考慮如何弄成鋸齒形狀。
如果要弄成鋸齒狀輸出,那麼控制下Console.WriteLine()和Console.Write()的時機就可以完成,但這裡是要求列印鋸齒狀後的每一行。
以行數為5為例,觀察上面鋸齒的產生過程,對於斜行,其都是夾在相鄰兩行起點和終點之間的,那麼斜行的元素個數必然會少2。即斜行的元素個數都是行數rows-2。這個因素定下來了,後面的就不會順序就不會有改變了。
跑分90%。
public string Convert(string s, int numRows) {
if (numRows == 1)
{
return s;
}
char[] arr = s.ToCharArray();
StringBuilder result = new StringBuilder();
StringBuilder[] str = new StringBuilder[numRows];
for (int i = 0; i < str.Length; i++)
{
str[i] = new StringBuilder();
}
int index = 0;
int j = 0;
while (index < arr.Length)
{
for (j = 0; index < arr.Length && j < numRows; index++)
{
str[j++].Append(arr[index]);
}
for (j = numRows - 2; index < arr.Length && j > 0; )
{
str[j--].Append(arr[index++]);
}
}
for(int i = 0; i < str.Length; i++)
{
result.Append(str[i].ToString());
}
return result.ToString();
}
7.Reverse Integer
能用%,/運算子分割出每一位就別用轉換成字串來描述。能用每次取個位後乘10升位來反轉就別用字串來對字元做交換。
做超限處理。
注意int的表示範圍。
跑分30%。低。。
public int Reverse(int x) {
Int64 result = 0;
while (x != 0)
{
int i = x % 10;
result = result * 10 + i;
if (result > Int32.MaxValue || result < Int32.MinValue)
{
result = 0;
break;
}
x /= 10;
}
return (int)result;
}
8.String to Integer (atoi)
這道題提交了很多次,題沒審清,英文描述沒去細細讀。
int超界是個頭疼的事情,用Int64來裝都被一個測試用例超界反轉了。
最後跑分已慘不忍睹。還得改。
public int MyAtoi(string str) {
str = str.Trim();
if (string.IsNullOrEmpty(str))
{
return 0;
}
char[] arr = str.ToCharArray();
Int64 result = 0;
int bit = 0;
bool negative = false;
if (!(arr[0] == 43 || arr[0] == 45 || arr[0] > 47 && arr[0] < 58))
{
return 0;
}
if (arr[0] == 43 || arr[0] == 45)
{
if (arr.Length == 1 || !(arr[1] > 47 && arr[1] < 58))
{
return 0;
}
}
if (arr[0] == 45)
{
negative = true;
}
for (int i = 0; i < arr.Length; i++)
{
if (arr[i] > 47 && arr[i] < 58)
{
bit++;
result = result * 10 + int.Parse(arr[i].ToString());
if (i < arr.Length - 1)
{
if (arr[i + 1] <= 47 || arr[i + 1] >= 58)
{
break;
}
}
}
}
if (negative)
{
result *= -1;
}
if (bit > 10 || result > Int32.MaxValue || result < Int32.MinValue)
{
result = negative ? Int32.MinValue : Int32.MaxValue;
}
return (int)result;
}
9.Palindrome Number
相對於第5題求最長迴文子串,這道題就簡單很多了,因為開始結束位置都是確定了的。
跑分60%。
public bool IsPalindrome(int x) {
//// -109 和 901-才是迴文
if (x < 0)
{
return false;
}
int ori = x;
int res = 0;
while (ori > 0)
{
res = res * 10 + ori % 10;
ori /= 10;
}
return res == x;
}
1-9道演算法題的記錄到這裡就完成了。後面再繼續了。