leetcode:线性表、哈希表

注意:
对于链表而言,不要忘记边界条件,即

if(head==NULL || head->next==NULL) return head;

链表节点的实现一般都用指针,所以应该用->操作符,而不是 . 操作符。
判断指针是否为空,要用if(p!=NULL) ,而不要用if(!p) ,后者在leetcode中会报错。

206. 反转单链表

思路
反转单链表的方法有递归和非递归两种。
递归:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL || head->next==NULL) return head;  

        ListNode* p = head->next;  
        ListNode* n = reverseList(p);//调用递归

        head->next = NULL;  
        p->next = head;  
        return n;  
    }  
};

非递归:
while循环中,完成对当前节点的next指针的重定向。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL || head->next==NULL) return head;  

        ListNode* pre = head;  
        ListNode* p = head->next;  
        ListNode* nxt = NULL; 

        pre->next = NULL; 
        while(p!=NULL) {  
            nxt = p->next;  
            p->next = pre;  
            pre = p;  
            p = nxt;  
        }  
        return pre;  
    }  
};  

136. 单独出现的数(single number)

现有一串整数,除了一个数之外,其余的数都出现了2次。现要找出那个单独出现的数。
思路
利用异或^的性质:x^x=0和0^x=x

        int x=nums[0];
        for(int i=1;i<nums.size();i++)
            x^=nums[i];
        return x;

137. 单独出现的数(2)

现有一串整数,除了一个数之外,其余的数都出现了3次。现要找出那个单独出现的数。
思路
考虑每个元素都是一个32位的二进制数,这样每一位上出现要么为1 ,要么为0。对数组,统计每一位上1 出现的次数count,必定是3N或者3N+1 次。让count对3取模,能够得知那个只出现1次的元素该位是0还是1。最后,将32位的各位组合起来。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int result = 0;  

        for(int i = 0; i<32; i++){  
            int count = 0;   
            int mask = 1<< i;  
            for(int j=0; j<nums.size(); j++){  
                if(nums[j] & mask)  
                    count++;  
            }  
          if(count %3)  
                result |= mask;  
        }  
        return result;  
    }  
};  

事实上,该思路同样适合对题136的解答,只要把if(count%3)改成if(count%2)

260. 单独出现的数(3)

与题136不同的是,有2个单独出现的数,要求将它们找出来。
思路
在题136的基础上,将所有数异或1次得到的x其实是那两个单独出现的数a和b的异或结果。
如果x的某一位是1,可以根据该位将原数列分为两组,a和b被分在不同的组。
接着,对两组再分别进行1次异或,就能分别得到a和b的值。
技巧
虽然x中每个1的位置都能用来分类,但取不为零的最低位最为简单。x&(~(x-1))可以取出x中不为零的最低位。

    vector<int> singleNumber(vector<int>& nums) {
        int x=0;
        for(int i=0;i<nums.size();i++)
            x^=nums[i];
        int y=x&(~(x-1));
        int a=0,b=0;
        for(int i=0;i<nums.size();i++)
            if(y&nums[i])
            a^=nums[i];
            else
            b^=nums[i];
        return vector<int>({a,b});

    }

237. 删除链表节点

给定一个单链节点的链接,将其删除。
思路
用下一节点把该节点覆盖即可。
但是还要考虑到,如果要删除的节点位于链表的尾部,那么还是只能从链表的头结点开始,顺序遍历得到该节点的前序节点,然后删除。
另外,如果链表中只有一个节点,而我们又要删除它,此时我们需要把链表的头结点设置为NULL。

void DeleteNode (ListNode** pListHead, ListNode* pToBeDeleted)
{
    if(!pListHead || !pToBeDeleted) return;//如果头结点或者要删除的节点是空的

    //如果要删除的节点不是尾节点
    if(pToBeDeleted->next != NULL) 
    {
        ListNode* pNext = pToBeDeleted->next;
        pToBeDeleted->value = pNext->value;
        pToBeDeleted->next = pNext->next;

        delete pNext;
        pNext = NULL;
    }
    //如果链表只有一个节点
    else if(*pListHead == pToBeDeleted)
    {
        delete pToBeDeleted;
        pToBeDeleted = NULL;
        *pListHead = NULL;
    }
    //链表中有多个节点,要删除尾节点
    else
    {
        ListNode* pNode = *pListHead;
        while(pNode->next != pToBeDeleted)
            pNode = pNode->next;
        pNode->next = NULL;
        delete pToBeDeleted;
        pToBeDeleted = NULL;
    }
}

217. 判断是否存在重复的数

判断给定一组数中,是否存在重复2遍及以上的数,若有,则返回true,否则返回false。
思路
利用set,统计数的出现次数。
遍历数列,如果出现重复2次的数,就返回true。

    bool containsDuplicate(vector<int>& nums) {
        set<int> m;
        for(int i=0;i<nums.size();i++){
            if(m.count(nums[i])==1) return true;
            m.insert(nums[i]);
        }
        return false;
    }

268. 消失的数

从0,1,2,……,n中取n个数,组成数列,求消失的那个数。
思路
先将数列nums升序排序,然后将nums[i]与i进行比较,若不相等,则找到了消失的数 i。

    int missingNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        for(int i=0;i < nums.size();i++){
            if(nums[i]!=i) return i;
        }
    }

328. 分离奇偶链表

给定一个单链表,将所有的奇节点归为一组,偶节点紧随其后。
注意要处理的是奇节点而不是节点上的值。

思路
用tmpodd、tmpeven、even、current共4个指针和1个奇偶标志位isodd一起遍历链表:

  1. 先把奇节点和偶节点分别、依次串起来;
  2. 然后,将偶数链表的头接在奇数链表的尾后,并将最后一个偶数节点的尾指向NULL。
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head==NULL || head->next==NULL ||head->next->next==NULL) return head;
        ListNode* tmpOdd = head;
        ListNode* tmpEven = head->next;
        ListNode* Even = tmpEven;
        ListNode* current = tmpEven->next;
        bool isOdd = true;
        while (current != NULL) {//当前节点非空
            if (isOdd) {//如果是奇节点
                tmpOdd->next = current;
                tmpOdd = current;
            } else {//如果是偶节点
                tmpEven->next = current;
                tmpEven = current;
            }
            current = current->next;
            isOdd = !isOdd;
        }
        tmpOdd->next = Even;//将偶数串接在奇数串后面
        tmpEven->next = NULL;//在偶数串的结尾接上一个NULL
        return head;
    }
};

141. 判断链表是否存在环

给定一个链表,判断它是否存在环。
思路
题目要求不用extra space。
设置两个指针first, second遍历链表,fast位置靠前,而且跑得快;second出发点靠后,而且跑得慢。
如果linked list成环,那么在环里,慢指针终将被快指针追上。如果最后fast跑完也没有追到slow,那就说明不存在环。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL || head->next==NULL) return false;
        ListNode* slow=head;    
        ListNode* fast=head->next;
        while(fast!=NULL){
            if(fast==slow) return true;
            //如果fast->next是NULL,说明不存在环
            if(fast->next == NULL) return false;
            fast=fast->next->next;
            slow=slow->next;
        }
        return false;
    }
};

这里设置快慢指针的起始位置不一样,起始也可以设置它们都从head出发,写法是:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL || head->next==NULL) return false;
        ListNode* slow=head;    
        ListNode* fast=head;
        while(fast!=NULL){
            if(fast->next == NULL) return false;
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow) return true;
        }
        return false;
    }
};

142. 判断链表是否存在环II

在上题的基础上,还要求返回环开始的那个节点。
思路:
这里写图片描述

可以推知,当fast指针以2步,slow指针以1步的速度同时前进时,两个指针在z处相遇,而a和c是同样长的。
因此,如果此时,我们再让两个指针p和q分别从X和Z出发,并且每次都移动一步,当它们相遇时,恰好就是环的起点Y。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution
{
public:
    ListNode *detectCycle(ListNode *head)
    {
        auto fast = head;
        auto slow = head;
        while (fast != nullptr && fast->next != nullptr)
        {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow)
            {
                auto p = head;
                auto q = fast;
                while (p != q)
                {
                    p = p->next;
                    q = q->next;
                }
                return p;
            }
        }
        return nullptr;
    }
};

21. 合并两个排好序的单链表

思路
首先注意边界条件,如果l1或l2中有一个为空,就直接返回。

递归实现:

class Solution {
public:
    ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) {
        if(l1 == NULL) return l2;
        if(l2 == NULL) return l1;

        if(l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l2->next, l1);
            return l2;
        }
    }
};

非递归实现:

class Solution {  
public:  
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {  
        if(l1 == NULL) return l2;
        if(l2 == NULL) return l1;

        ListNode* tmp = NULL;
        if(l1->val < l2->val)
        {
            tmp = l1;
            l1 = l1->next;
        }else
        {
            tmp = l2;
            l2 = l2->next;
        }
        ListNode* head = tmp;

        while (l1 && l2)  
        {  
            if (l1->val < l2->val)  
            {  
                tmp->next = l1;  
                l1 = l1->next;  
            }else{  
                tmp->next = l2;  
                l2 = l2->next;  
            }  
            tmp = tmp->next;  
        }
        //如果l1或l2空了,就直接链接到表尾
        if (l1)  
            tmp->next = l1;  
        if(l2)  
            tmp->next = l2;  
        return head;  
    }  
};

264. 丑数(Java)

Write a program to find the n-th ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.

Note that 1 is typically treated as an ugly number.
思路
建立3个辅助数组,每个数组分别负责存储当前丑数乘以2,3,5的值。下一个丑数是这么取的:每次从数组的头分别取出1个数,表示3个数组各自的最小值,然后从这3个数中选取最小值,作为新的丑数。然后,将新的丑数分别乘以2,3,5,放入数组中。

public class Solution {  
    public int nthUglyNumber(int n) {  
        int u = 0;  
        List<Integer> l1 = new LinkedList<Integer>();  
        List<Integer> l2 = new LinkedList<Integer>();  
        List<Integer> l3 = new LinkedList<Integer>();  
        l1.add(1);  
        l2.add(1);  
        l3.add(1);  

        for(int i=0; i<n; i++) {
            //取三者的最小值
            u = Math.min( Math.min(l1.get(0), l2.get(0)), l3.get(0));  

            if(l1.get(0) == u) l1.remove(0);  
            if(l2.get(0) == u) l2.remove(0);  
            if(l3.get(0) == u) l3.remove(0);  

            l1.add(u*2);  
            l2.add(u*3);  
            l3.add(u*5);  
        }  
        return u;  
    }  
}  

313. 超级丑数(Super Ugly Number)

Write a program to find the nth super ugly number.

Super ugly numbers are positive numbers whose all prime factors are in the given prime list primes of size k. For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32]is the sequence of the first 12 super ugly numbers given primes = [2, 7, 13, 19] of size 4.

思路
除了题目提供的primes,还要建立两个数列:num保存已经生成的超级丑数;pos[i]用以保存num中可能可以与primes[i]相乘,生成下一个超级丑数的坐标位置
下图中,
这里写图片描述

已知的超级丑数保存在num(ugly)中。下一个超级丑数是这样得到的:将ugly和prime的元素两两相乘,所有乘积中最小的即为下一个丑数。
为了优化程序,pos中保存了primes[i]对应与之相乘的ugly[i]的位置,因为该位置之前的元素都已经被排除了。

class Solution {  
public:  
    int nthSuperUglyNumber(int n, vector<int>& primes) {  
        int length=primes.size();
        vector<int> nums(n, INT_MAX), pos(length, 0);  

        nums[0] = 1; 
        for(int cur = 1; cur< n; cur++)  
        {
            //确定第cur个丑数
            for(int i = 0; i < length; i++)
                nums[cur] = min(nums[cur], nums[pos[i]] * primes[i]);  
            //更新pos     
            for(int i = 0; i < length; i++)
                if( (nums[pos[i]]*primes[i]) == nums[cur] )
                  pos[i]++;
        }  
        return nums[n-1];  
    }  
};  

334. 长度为3的递增子序列(Increasing Triplet Subsequence)

判断一段序列中,是否存在长度为3的递增子序列(不需要连续)。
思路
从头到尾遍历序列,用a表示迄今为止最小的数,b表示第2大的数。
用3个if分支,就可以解决问题。

  • if(nums[i]<=a) a=nums[i]:更新a;
  • 否则,else if(nums[i]<=b) b=nums[i]:更新b;
  • 否则,说明a<b<nums[i],已经符合题意,可以直接返回。
class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int length=nums.size();
        if(length<3) return false;
        int a=INT_MAX,b=INT_MAX;
        for(int i=0;i<length;i++)
        {
            if(nums[i]<=a) a=nums[i];
            else if(nums[i]<=b) b=nums[i];
            else return true;
        }
        return false;
    }
};

42. 最大蓄雨水量(Trapping Rain Water)

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
这里写图片描述

思路
这道题求最多能装多少水。其中,数组中的数字表示高度。思路是采用l和r两个指针,记录装水两边的位置。

  1. 从左右两边往中间遍历,首先判断l和r哪个更低;
  2. 若l处更低,说明在l右侧(如果能装水)水位一定等于l。于是右移l,直到到达同样高或者更高的位置。在此过程中,(如果有的话)加上右移过程中的高度差;
  3. 若r处更低,步骤类似;
  4. 重复以上步骤,直到l和r相遇,结束。
class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0, l = 0, r = height.size() - 1;
        while(l < r)
        {
            int minh = min(height[l], height[r]);
             if(height[l] == minh)
                 while(++ l < r && height[l] < minh)
                     res += minh - height[l];
             else
                 while(l < -- r && height[r] < minh)
                     res += minh - height[r];
         }

         return res;        

    }
};

407. 最大蓄雨水量II(Java)

和上题的不同之处在于这里是一个存储高度的二维数组,同样要求求最大蓄水量。
题意:
二维的原理和一维的思路基本是一样的,思想是:

  1. 构造一个优先队列,每次都取出高度最矮的一个。
  2. 首先将四周的桩都加入,因为最外的四周是无法盛水的 。
  3. 每次从优先队列中取出一个最矮的cell,若他比未访问过的四周的四个高了delta h,那么总的盛水量多加delta h,否则不加,注意四周只要一访问过下次就不能访问了。

因为若当前cell被取出,且其某个邻居cell-n高度低于cell,那么可以得出:cell-n的邻居里面最矮的就是cell,所以cell可以决定cell-n的盛水量。

public class Solution {
    public int trapRainWater(int[][] heightMap) {
        //一个单元格用一个Cell来表示
        class Cell {
            int x, y,h;
            Cell(int x, int y, int height){
                this.x = x;
                this.y = y;
                h = height;
            }
        }
        if (heightMap == null || heightMap.length == 0 || heightMap[0].length == 0)  return 0;
        int m = heightMap.length;
        int n = heightMap[0].length;
        //优先队列,每次按照优先度输出队列,而不是按照顺序,这里是每次输出最矮的哪一个
        PriorityQueue<Cell> pq = new PriorityQueue<>((v1,v2)->v1.h - v2.h);
        boolean[][] visited = new boolean[m][n];
        //将四周初始化为访问过的,周围的一边是怎么都没法盛水的
        for(int i = 0; i < n; i++) {
            visited[0][i] = true;
            visited[m-1][i] = true;
            pq.offer(new Cell(0, i, heightMap[0][i]));
            pq.offer(new Cell(m-1, i, heightMap[m-1][i]));
        }
        for(int i = 1; i < m-1; i++){
            visited[i][0] = true;
            visited[i][n-1] = true;
            pq.offer(new Cell(i, 0, heightMap[i][0]));
            pq.offer(new Cell(i, n-1, heightMap[i][n-1]));
        }
        //四个方向
        int[] xs = {0,  0, 1, -1};
        int[] ys = {1, -1, 0,  0};
        int sum = 0;
        //开始计算收集到的雨水,每次取出其中最小高度,然后计算差值,就是当前单元格可以容纳的了
        while (!pq.isEmpty()) {
            //取出最小高度
            Cell cell = pq.poll();
            for (int i = 0; i < 4; i++) {
                int nx = cell.x + xs[i];
                int ny = cell.y + ys[i];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                    visited[nx][ny] = true;
                    sum += Math.max(0, cell.h - heightMap[nx][ny]);
                    pq.offer(new Cell(nx, ny, Math.max(heightMap[nx][ny], cell.h)));
                }
            }
        }
        return sum;
    }
}

128. 最长连续序列的长度(Longest Consecutive Sequence)

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

思路
一开始想到的办法是:先排序,再找结果,但是时间复杂度要求 O(n) ,使用排序会超时。
正确的思路是:

  1. 先将所有元素塞入一个set中;
  2. 取出 set 中的第一个元素x,如果set中还存在x顺序往后的元素,则依次删除之,并记录连续的长度;
  3. 若 set 还有元素,则继续上述步骤;
  4. set变空后,取出连续长度中的最大值。
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        set<int> s;
        int res=0;
        //先把元素一个个压进set
        for(int i=0;i<nums.size();i++)
        {
            s.insert(nums[i]);
        }
        while(!s.empty())
        {   
            int x = *(s.begin());
            s.erase(x);

            int cnt = 1;
            x++;
            //如果set中有(x+1)
            while (s.count(x))
            {
                s.erase(x);
                cnt++;
                x++;
            }
             res = max( res, cnt);

        }
        return res;
    }
};

347. 频次最高的前K个元素(Top K Frequent Elements)

Given a non-empty array of integers, return the k most frequent elements.

For example, 
Given [1,1,1,2,2,3] and k = 2, return [1,2].

Note: 
You may assume k is always valid, 1 ≤ k ≤ number of unique elements. 
Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size. 
Subscribe to see which companies asked this question

给出一个非空整数序列,返回出现频次最大的k个元素。
思路
建立哈希表。

  1. 首先用map得到<元素,频次>键值对;
  2. 然后建立哈希表,大小为n+1的二维数组,数组下标为频次,数组内容为有相同频次的元素;
  3. 对哈希表按下标由大到小遍历,找出前k个元素。
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> count;
        int length=nums.size();
        //建立map
        for(int i=0;i<length;i++)
            ++count[nums[i]];

        //建立哈希表
        vector<vector<int> > hasht(length+1,vector<int>());
        for(map<int,int>::iterator  it=count.begin();it!=count.end();++it)
        {
            hasht[it->second].push_back(it->first);
        }

        vector<int> re;
        int num = 0;
        for (vector<vector<int> >::reverse_iterator it=hasht.rbegin(); it != hasht.rend(); it++) 
        {
            for(int i=0;i<(*it).size();i++)
            {
                re.push_back((*it)[i]);
                num++;
                //如果已经够k个了
                if(num == k)
                    return re;
            }

        }
    }
};

406. 重构队列(Java)

思路
首先是排序,排序的规则是先根据第一个数,再根据第二个数进行排序,即

people: 
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] 

排序后:

[[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]] 

然后从数组people第一个元素开始,放入到数组result中,放入的位置距离当前result[0]的偏移量即为第二个数。具体如下:
1. people: [7,0]
插入到离开始位置偏移了0个距离的位置。
result: [[7,0]]
2. people: [7,1]
插入到离开始位置偏移了1个距离的位置,即插入到[7,0]的后面。
result: [[7,0], [7,1]]
3. people: [6,1]
插入到离开始位置偏移了1个距离的位置,即插入到[7,0]的后面。
result: [[7,0], [6,1], [7,1]]
4. people: [5,0]
插入到离开始位置偏移了0个距离的位置,即插入到[7,0]的前面。
result: [[5,0], [7,0], [6,1], [7,1]]
5. people: [5,2]
插入到离开始位置偏移了2个距离的位置,即插入到[7,0]的后面。
result: [[5,0], [7,0], [5,2], [6,1], [7,1]]
5. people: [4,4]
插入到离开始位置偏移了4个距离的位置,即插入到[6,1]的后面。
result: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

这种算法确保了:每次插入元素e时,result中现有的元素都大于等于e。因此e的位置可以由e的第二个数得到。另一方面,在e后面插入的数,无论其位置,因为都小于e,所以不会造成问题。

public class Solution {
    private void swap(int[][] people,int a,int b){
        int t1=people[a][0],t2=people[a][1];
        people[a][0] = people[b][0];
        people[a][1] = people[b][1];
        people[b][0] = t1;
        people[b][1] = t2;
    }
    public int[][] reconstructQueue(int[][] people) {
        //java 排序不方便,我这里就直接冒泡排序了
        int n = people.length;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(people[i][0] < people[j][0])
                    swap(people,i,j);
                else if(people[i][0] == people[j][0] && people[i][1] > people[j][1])
                    swap(people,i,j);
            }
        }
        //按照顺序插入
        ArrayList<Integer> la = new ArrayList<Integer>();
        ArrayList<Integer> lb = new ArrayList<Integer>();
        for(int i=0;i<n;i++){
            la.add(people[i][1],people[i][0]);
            lb.add(people[i][1],people[i][1]);
        }
        for(int i=0;i<n;i++){
            people[i][0]=la.get(i);
            people[i][1]=lb.get(i);
        }
        return people;

    }
}

384. 数列随机洗牌(Java)

题意

// Init an array with set 1, 2, and 3.
int[] nums = {1,2,3};
Solution solution = new Solution(nums);

// Shuffle the array [1,2,3] and return its result. Any permutation of [1,2,3] must equally likely to be returned.
solution.shuffle();

// Resets the array back to its original configuration [1,2,3].
solution.reset();

// Returns the random shuffling of array [1,2,3].
solution.shuffle();

代码

public class Solution {

    int[] init;  
    public Solution(int[] nums) {  
        init=nums.clone();  
    }  

    /** Resets the array to its original configuration and return it. */  
    public int[] reset() {  
        return init;  
    }  

    /** Returns a random shuffling of the array. */  
    public int[] shuffle() {  
        int[] random=new int[init.length];  
        for (int i = 0; i < random.length; i++) {  
            random[i]=i;  
        }  
        Random r=new Random();  
        for (int i = random.length-1; i >= 0 ; i--) {  
            int t=r.nextInt(i+1);  
            int swap=random[i];  
            random[i]=random[t];  
            random[t]=swap;  
        }  
        for (int i = 0; i < random.length; i++) {  
            random[i]=init[random[i]];  
        }  
        return random;  
    }  
}  

390. 消除游戏(Java)

题意:
所谓的消除游戏,是指给出一个数字n,对应1..n的序列,然后重复如下流程:
1、选择当前序列的第1,3,5,7…..的所有奇数位置的数字消除,得到新的序列
2、从后往前。选择当前序列的倒数第1,3,5,7…这些倒数奇数位置的数字消除,得到新的序列
3、重复1和2 直到只剩一个为止
思路:
这道题首先可以推出一个规律:
无论是1还是2,若当前序列长度为k,那么下一轮一定只剩k/2(取整)个。
然后,对于数列1,2,3,4,5,6..n,第一轮结束剩下2,4,6,8,10….2*(n/2) ,它等于2*(1,2,3,4…n/2),但这个序列要从右往左处理。
对于下一轮而言,虽然是1,2,3,4…,n/2,但是顺序是相反的。既然这样,我们可以按照数列n/2,…,4,3,2,1处理,顺序依然是从左往右,然后对于得到的结果再处理一步:n/2 + 1 - lastremain
思路比较巧妙,而实际的代码只有一行。

public class Solution {
    public int lastRemaining(int n) {
        return n==1?1:2*(n/2+1-lastRemaining(n/2));
    }
}

88. 合并两个数组(Java)

题意:
给两个数组n1[1,2,3],n2[3,4,5],把n2合并到n1中去,n1长度>=n1.length+m.length
思路:
这道题从尾部开始比较,大的放到n1数组的最后一位。并把大数所在的数组尾部指针往前移,直到比较结束后,所有数就自然的在n1中排序了。

public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
         if(nums1==null||nums2==null||m<0||n<0) return;

         int index = m+n-1;
         int index1 = m-1;
         int index2 = n-1;
         while(index1>=0&&index2>=0) 
         {
            if(nums1[index1]<nums2[index2]) 
                nums1[index--] = nums2[index2--];
            else 
                nums1[index--] = nums1[index1--];
         }
         while(index1>=0) 
             nums1[index--] = nums1[index1--];
         while(index2>=0) 
             nums1[index--] = nums2[index2--];
        }
}

23. 合并k个链表(Java)

将k个已经排序的链表合并成一个排序的链表,分析并描述所用算法的复杂度。
思路:
基于“二分”思想的归并排序。
初见之下,最容易想到的方法是“归并排序”(Merging Sort):将两个或两个以上的有序表组合成一个新的有序表,无论是顺序存储结构还是链式存储结构,对于任何两个长度分别为m和n的有序表,其组合都可在O(m+n)的时间复杂度量级上完成。
对于K个有序表,假设共有N个元素,且这些有序表初始状态都不为空,每个有序表平均拥有N/K个元素。最常用的方法是采用“二分”的思想进行两两合并:第一轮循环,有序表lists[0]与lists[(K+1)/2],lists[1]与lists[(K+1)/2+1],lists[2]与lists[(K+1)/2+2]….,lists[K/2-1]与lists[K-1]。这样K个有序表就被组合成了K/2个有序表;第二轮循环后将减少为K/4个有序表;直到组合成一个具有N个元素的有序表。总的时间复杂度:O(NKlogK)。

/** 
 * Definition for singly-linked list. 
 * public class ListNode { 
 *     int val; 
 *     ListNode next; 
 *     ListNode(int x) { val = x; } 
 * } 
 */  
class Solution  
{  
public ListNode mergeKLists(ListNode[] lists) {  
        int len=lists.length;  
        if(lists==null||len==0)  
            return null;  
        if(len==1)  
            return lists[0];  

        while(len>1)//基于“二分”思想进行链表的两两组合  
        {  
            int mid=(len+1)/2;//二分  
            for(int i=0;i<len/2;i++)  
            {  
                lists[i]=mergeTwoLists(lists[i],lists[i+mid]);  
            }  
            len=mid;  
        }  
        return lists[0];  
    }  
    //有序链表的组合,L1和L2为头结点,归并排序的核心思想  
    public ListNode mergeTwoLists(ListNode L1,ListNode L2)  
    {  
        if(L1==null)return L2;  
        if(L2==null)return L1;  

        ListNode head=new ListNode(0);//保存结果的链表,头结点初始化  
        ListNode phead=head;  

        while(L1!=null&&L2!=null)//两个链表归并排序  
        {  
            if(L1.val <=L2.val)  
            {  
                phead.next=L1;//接入结果链表  
                phead=phead.next;//移动指针  
                L1=L1.next;  
            }  
            else  
            {  
                phead.next=L2;  
                phead=phead.next;  
                L2=L2.next;  
            }  
        }  
        if(L1!=null)  
            phead.next=L1;  
        else  
            phead.next=L2;  

        return head.next;//初始化的第一个节点不属于结果  
    }  
} 

160. 两个单链表的共同节点(Java)

思路:
方法一:
第一遍循环,找出两个链表的长度差N
第二遍循环,长链表先走N步,然后同时移动,判断是否有相同节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lengthA=0;
        int lengthB=0;
        ListNode p=headA;
        while(p!=null) {
            p=p.next;
            lengthA++;
        }
        ListNode q=headB;
        while(q!=null) {
            q=q.next;
            lengthB++;
        }
        p=headA;
        q=headB;
        if(lengthA>=lengthB) {
            int e=lengthA-lengthB;
            while(e!=0) {
               p=p.next;
               e--;
            }
            while(p!=null) {
                if(p==q) return p;
                p=p.next;
                q=q.next;
            }
        }
        if(lengthB>lengthA) {
            int e=lengthB-lengthA;
            while(e!=0) {
               q=q.next;
               e--;
            }
            while(p!=null) {
                if(p==q) return p;
                p=p.next;
                q=q.next;
            }
        }
        return null;
    }
}

方法二:
分别把两个链表的结点放入两个栈中,这样两个链表的尾结点就位于两个栈的栈顶。接下来比较两个栈顶的结点是否相同,如果相同,则把栈顶弹出,接着比较下一个栈顶,直到找到最后一个相同的结点。

421. 序列中两数异或的最大值(Java)

给定一列数字,a[0], a[1], a[2], … , a[N-1],其中0 <= a[i] < 2^32。计算a[i] XOR a[j]的最大值。
思路:
很难想到,利用异或的性质:a ^ b = c,则 c ^ b = a。
从高位开始,确定每一位是1还是0。具体见注释。

PS:
这道题也可以用01字典树来解决,而且更直观。具体见“Trie树”篇。

public class Solution {
    public int findMaximumXOR(int[] nums) {
        int max = 0, mask = 0;
        //从最高位开始,考察每一位应该是1还是0
        for(int i = 31; i >= 0; i--){
            //mask用于取出nums中每个数的前x位
            mask = mask | (1 << i);
            Set<Integer> set = new HashSet<>();
            for(int num : nums){
                set.add(num & mask);
            }
            //max是上一步确认的最终结果的前x-1位
            //tmp是可能的最终结果的前x位,第x位为1
            int tmp = max | (1 << i);
            //如何判断第x位?如果a^b=c,那么有a^c=b
            //所以,如果tmp确实是最终结果,那么它应该是set中某两个数的异或结果,否则,第x位为0
            for(int prefix : set){
                if(set.contains(tmp ^ prefix)) {
                    max = tmp;
                    break;
                }
            }
        }
        return max;
    }
}

15. 三个数的和为0

题意:从给定的数组中找三个数,让它们的和为0。输出所有可能。
如[1,3,-1,0,-3],那么输出[1,-1,0],[3,0,-3]。
思路:首先进行排序。然后从数组第一位开始遍历,如第一位为1,在剩余后面的数组[3,-1,0,-3]中找出和为-1的两个数。
如何在剩余的数组中找出和为-1的数?方法是用两个指针,分别指向该剩余数组的头和尾,并算出头和尾的和s,再把s与k比较,如果s小于k,头指针往后移,如果s大于k,尾指针往前移。直到找到为止。如果头尾指针相遇还没找到,则证明不存在。
注意,遇到重复的元素时continue,可以提高速度。

public class Solution {  
    public List<List<Integer>> threeSum(int[] nums) {  
        List<List<Integer>> res = new ArrayList<List<Integer>>();  
        int len=nums.length;  
        if(len<3)return res;  
        Arrays.sort(nums);  
        for(int i=0;i<len;i++){  
            if(nums[i]>0)break;  
            if(i>0 && nums[i]==nums[i-1])continue;  
            int begin=i+1,end=len-1;  
            while(begin<end){  
                int sum=nums[i]+nums[begin]+nums[end];  
                if(sum==0){  
                    List<Integer> list = new ArrayList<Integer>();  
                    list.add(nums[i]);list.add(nums[begin]);list.add(nums[end]);  
                    res.add(list);  
                    begin++;end--;  
                    while(begin<end && nums[begin]==nums[begin-1])begin++;  
                    while(begin<end && nums[end]==nums[end+1])end--;  
                }else if(sum>0)end--;  
                else begin++;  
            }  
        }  
        return res;  
    }  
}  

234. 判断单链表是否是回文链表

要求时间复杂度O(n),空间复杂度O(1)。

思路:
将原单链表分成长度相同(如果是奇数个节点,就长度差1)的两部分, 将其中一个翻转,再一一比较。
值得一提的是,这里寻找单链表的中间位置,可以用快慢指针。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null || head.next==null) return true;
        //先利用快慢指针,定位到单链表的中间位置
        ListNode middle = partition(head);
        //从中间位置开始,将后半部分的链表翻转
        middle = reverse(middle);

        while(head!=null && middle!=null) {
            if(head.val != middle.val) return false;
            head = head.next;
            middle = middle.next;
        }
        return true;
    }
    private ListNode partition(ListNode head) {
        ListNode p = head;
        while(p.next!=null && p.next.next!=null) {
            p = p.next.next;
            head = head.next;
        }

        p = head.next;
        head.next = null;
        return p;
    }
    private ListNode reverse(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode pre = head;
        ListNode cur = head.next;
        pre.next = null;
        ListNode nxt = null;

        while(cur!=null) {
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
}

307. 区间和查询

给定一个数列,要求快速查询某个区间的和。
思路:
这里要用到一个比较高级的数据结构:线段树。

public class NumArray {  

    class TreeNode {  
        int start = 0;  
        int end = 0;  
        int sum = 0;  
        TreeNode left = null;  
        TreeNode right = null;  
    }  
    TreeNode root = null;  
    public void init(int[] nums) {  
        if(nums == null || nums.length == 0) return;  
        this.root = buildTree(0, nums.length-1, nums);  
    }  
    public TreeNode buildTree(int start, int end, int[] data) {  
        TreeNode t = new TreeNode();  
        t.start = start;  
        t.end = end;  
        if(start == end) {  
            t.sum = data[start];  
            return t;  
        }          
        int mid = start + (end-start)/2;  
        t.left = buildTree(start, mid, data);  
        t.right = buildTree(mid+1, end, data);  
        t.sum = t.left.sum + t.right.sum;  
        return t;  
    }  
    public NumArray(int[] nums) {  
        init(nums);  
    }  

    public void updateTree(TreeNode node, int index, int val) {  
        if(node == null) return;  
        if(node.start == node.end) {  
            node.sum = val;  
            return;  
        }  
        int mid = node.start + (node.end - node.start)/2;  
        if(index<=mid) {  
            updateTree(node.left, index, val);  
        }else {  
            updateTree(node.right, index, val);  
        }  
        node.sum = node.left.sum + node.right.sum;  
    }  

    void update(int i, int val) {  
        updateTree(root, i, val);  
    }  
    public int queryTree(TreeNode node, int left, int right) {  
        if(node == null) return 0;  
        if(node.start == left && node.end == right) return node.sum;  
        int mid = node.start + (node.end - node.start)/2;  
        if(mid>=right) {  
            return queryTree(node.left, left, right);  
        }else if(mid<left) {  
            return queryTree(node.right, left, right);  
        }else {  
            return queryTree(node.left, left, mid) + queryTree(node.right, mid+1, right);  
        }  
    }  
    public int sumRange(int i, int j) {  
        return queryTree(root, i, j);  
    }  
}  


// Your NumArray object will be instantiated and called as such:  
// NumArray numArray = new NumArray(nums);  
// numArray.sumRange(0, 1);  
// numArray.update(1, 10);  
// numArray.sumRange(1, 2); 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值