Categories
不学无术

LeetCode 319. Bulb Switcher 智商碾压题

There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every second bulb. On the third round, you toggle every third bulb (turning on if it’s off or turning off if it’s on). For the ith round, you toggle every i bulb. For the nth round, you only toggle the last bulb. Find how many bulbs are on after n rounds.
Example:

Given n = 3.
At first, the three bulbs are [off, off, off].
After first round, the three bulbs are [on, on, on].
After second round, the three bulbs are [on, off, on].
After third round, the three bulbs are [on, off, off].
So you should return 1, because there is only one bulb is on.

我的超时解

递推法,当从n-1到n时,新加上去的一次操作是判断从[1,n]里面n的因子个数,这里记作f(n)好了,即a[n] = a[n-1] + f(n)
因子个数的求法可以精简一下时间,排除0,1,2这三个特殊值后,计算[2, sqrt(n)]里头能整除的数的个数就行了,如果碰到个平方数那个数+1,不然个数就是+2的(i能被n整除,那么它的补数n/i也行)。
这个方法的复杂度嘛,O(n^1.5)吧

class Solution {
public:
    int factors(int n){
        if(n == 0){
            return 0;
        } else if(n == 1){
            return 1;
        } else if(n == 2){
            return 2;
        }
        int count = 2;  //1和自己
        int sqr = (int)(sqrt(n));
        for(int i=2; i <= sqr; i++){
            if(n % i == 0){
                if(i *i == n)
                    count += 1; //平方数
                else
                    count += 2; //一个数和它的补数
            }
        }
        return count;
    }
    int bulbSwitch(int n) {
        if(n < 1){
            return 0;
        }
        int bCount = 0;
        for(int i=1; i<=n; i++){
            if((factors(i)&1) == 1){
                bCount ++;
            }
        }
        return bCount;
    }
};

智商碾压,O(1)解

真正的奥义用一行代码就能解释

class Solution {
public:
    int bulbSwitch(int n) {
        return sqrt(n);
    }
};

呵呵哒!这个规律如果多试几次说不定能发现,0,1,1,1,2,2,2,2,2,3这种数列嘛
https://leetcode.com/discuss/91371/share-my-o-1-solution-with-explanation
大概意思是,要在[1,n]里头找哪些灯泡被执行了奇数次操作

  •  对于一个非平方数来说(比如12),因为有成对的补数(1vs12; 2vs6),所以总是会按下2的倍数次
  • 但是对于一个平方数来说(比如36),因为其中有个数(6)它的补数是自己,所以会按被下奇数次

然后这道题就变成了找[1,n]中间有几个平方数了
OMG…

Categories
不学无术

LeetCode 322. Coin Change

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)
Example 2:
coins = [2], amount = 3
return -1.
Note:
You may assume that you have an infinite number of each kind of coin.

思路

动态规划的问题,不过如果倒着做的话,似乎会超时…
所以考虑往前递推的情况,即当前要解决X元的找零问题的最佳解法且已知<X时的解法,手头有一堆面值为coins[i]的硬币的时候,对于每种可用的硬币我们要做的选择是:

  • 尝试使用coin[i],即当前的最优结果=(X-coins[i])元时的最优结果+1,这个1就是用了coins[i]这个硬币;
  • 保持现有结果不变

这两个方法当然是取最好的存下来了。

代码

class Solution {
public:
    int MAX_NUM = 65535;
    int coinChange(vector<int>& coins, int amount) {
        vector<int> solutions(amount+1, MAX_NUM); //0~amount,共amount+1个
        solutions[0] = 0;
        int coinSz = coins.size();
        for(int i=1; i<=amount; i++){
            for(int c=0; c<coinSz; c++){
                //得到当前的amount,要么就是用原有的结果,要么就是使用amount[i-coin] + 1个当前面值的
                if(coins[c] <= i){
                    solutions[i] = min(solutions[i], 1 + solutions[i-coins[c]]);
                }
            }
        }
        if(solutions[amount] == MAX_NUM)
            return -1;
        return solutions[amount];
    }
};

 

Categories
不学无术

C++ STL容器能力一览表

摘自《C++标准程序库 (The Standard C++ Library)》pp.227
未摘齐,需要详细可参考6.9节:各种容器的运用时机
STL_container_compare

Categories
不学无术

LeetCode 328. Odd Even Linked List

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.
You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.
Example:
Given 1->2->3->4->5->NULL,
return 1->3->5->2->4->NULL.
Note:
The relative order inside both the even and odd groups should remain as it was in the input.
The first node is considered odd, the second node even and so on …

思路

既然是要O(n)时间复杂度,并且O(1)空间复杂度,基本就能想到要用俩指针了。其中一个指针指向当前的奇数位元素,另一个指向偶数位的。
LeetCode328
大概就是这个意思啦,然后指针不断往前挪动。注意要保存偶数指针的头,因为奇数结束之后要串上偶数的~

代码

里面的oddTail是为了防止链表长度为奇数的时候,里面currOdd指针走到NULL的情况。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;
        ListNode* currOdd = head;
        ListNode* currEven = head->next;
        ListNode* evenHead = currEven;
        ListNode* oddTail = currOdd;
        while(currOdd != NULL && currEven != NULL){
            currOdd->next =currEven->next;
            if(currOdd->next != NULL)
                currEven->next = currOdd->next->next;
            oddTail = currOdd;
            currOdd = currOdd->next;
            currEven = currEven->next;
        }
        if(currOdd != NULL)
            currOdd->next = evenHead;
        else
            oddTail->next = evenHead;
        return head;
    }
};

 

Categories
不学无术

C++类型转换符 static_cast, dynamic_cast, const_cast

“cast”一词在英文里有“浇铸”的意思,还是挺形象的。

1.static_cast

可以看做是利用一个原始值构建一个临时对象,并再设定初始值的时候使用类型转换。
例如:

float x;
...
cout << static_cast<int>(x); //float to int
...
cout << static_cast<string>("hello")); //char* to string

2.dynamic_cast

将多态类型(polymorphic type)成员向下转换为其对应的静态类型成员。这个cast是在运行时实时检验的,因此可以用来检测某个指针指向的对象到底是不是XXX类的。

class Car; //abstract class
class Cabriolet : public Car {...};
class Limousine : public Car {...};
void f(Car* cp){
    Cabriolet* p = dynamic_cast<Cabriolet*>(cp);
    if(p == NULL) {
        //cp is not referred to an object of Cabriolet
    }
}

当参数是个引用,并且类别转换失败的时候,会抛出bad_cast异常哦。

3.const_cast

去除某个对象的const修饰作用,也可以拿来去除volatile修饰作用。
 

Categories
不学无术

C++异常处理,stack unwinding

这篇文章的内容摘自《C++标准程序库》2.2.3节
C++标准程序库可以在不污染函数接口的情况下处理异常。如果你遇到一个意外情况,可以跑出异常来停止后续流程,例如:

class Error;
void f()
{
    ...
    if(exception-condition)
        throw Error();
    }
    ...
}

其中throw开始了stack unwinding过程,也就是说,他将使得退离任何函数区段时的行为像以return语句返回一样,然而程序却不会跳转到任何地点。对于所有被声明与某区段——而该区段却因程序异常而退离——的局部对象而言,会调用它的析构函数stack unwinding的动作会持续到退出main()或被某个catch子句捕捉并处理了异常为止。
异常对象(exception objects)其实就是一般类或基本类的对象,可以是int,string类型的对象,也可以是某个模板类对象。
 

Categories
不学无术

M$ 2016 题目3 : Demo Day

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

You work as an intern at a robotics startup. Today is your company’s demo day. During the demo your company’s robot will be put in a maze and without any information about the maze, it should be able to find a way out.
The maze consists of N * M grids. Each grid is either empty(represented by ‘.’) or blocked by an obstacle(represented by ‘b’). The robot will be release at the top left corner and the exit is at the bottom right corner.
Unfortunately some sensors on the robot go crazy just before the demo starts. As a result, the robot can only repeats two operations alternatively: keep moving to the right until it can’t and keep moving to the bottom until it can’t. At the beginning, the robot keeps moving to the right.

rrrrbb..
...r....     ====> The robot route with broken sensors is marked by 'r'.
...rrb..
...bb...

While the FTEs(full-time employees) are busy working on the sensors, you try to save the demo day by rearranging the maze in such a way that even with the broken sensors the robot can reach the exit successfully. You can change a grid from empty to blocked and vice versa. So as not to arouse suspision, you want to change as few grids as possible. What is the mininum number?

输入

Line 1: N, M.
Line 2-N+1: the N * M maze.
For 20% of the data, N * M <= 16.
For 50% of the data, 1 <= N, M <= 8.
For 100% of the data, 1<= N, M <= 100.

输出

The minimum number of grids to be changed.

注记:

动态规划的一道题,后悔当时没有看通过率先做了第二道,第二题没有熟练掌握Trie结果处理超时了!
这题就是状态麻烦点,我的处理方式是在当前位置就判断右侧及下侧的通行情况,必要时做出改变,然后继续。因此注意点是一开始就被[0][0]位置blocked的情况。
以(x位置,y位置,当前方向)为一个三元组,可以保存唯一的状态结果,避免重复递归。

代码:

未经详细测试,待测试!感觉自己的编码能力还是有限,欢迎过客提出意见。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int FAIL = -32767;
struct St {
	bool hasRight;
	bool hasDown;
	int rightBest;
	int downBest;
	St(){
		hasRight = false;
		hasDown = false;
	}
	void setVal(bool movR, int val) {
		if (movR) {
			hasRight = true;
			rightBest = val;
		}
		else {
			hasDown = true;
			downBest = val;
		}
	}
	bool hasVal(bool movR) {
		if (movR)
			return hasRight;
		else
			return hasDown;
	}
	int getVal(bool movR) {
		if (movR)
			return rightBest;
		else
			return downBest;
	}
};
int legalMin(int x, int y) {
	if (x < 0) return y;
	else if (y < 0) return x;
	else return x<y ? x : y;
}
int moveMaze(const vector<string>& maze, int i, int j, bool movR, vector<vector<St>>& storedMap) {
	//cout << '.';
	if ((i == maze.size() - 1) && (j == maze[0].size() - 1)) {
		//Out of maze and finished
		return 0;
	}
	else if ((i == maze.size()) || (j == maze[0].size())) {
		//Reach boundary
		return FAIL;
	}
	if (storedMap[i][j].hasVal(movR)) {
		//cout << "HIT!" << i << ", " << j << ", "<< movR << endl;
		return storedMap[i][j].getVal(movR);
	}
	int right = FAIL;
	int down = FAIL;
	if (movR) {
		//Moving towards right side
		if (j == maze[0].size() - 1 || maze[i][j + 1] == '.') {
			right = moveMaze(maze, i, j + 1, movR, storedMap);
			if (j == maze[0].size() - 1)
				down = moveMaze(maze, i + 1, j, false, storedMap);
			else
				down = 1 + moveMaze(maze, i + 1, j, false, storedMap);
		}
		else {
			//blocked
			right = 1 + moveMaze(maze, i, j + 1, movR, storedMap);
			if (i == maze.size() - 1 || maze[i + 1][j] == '.')
				down = moveMaze(maze, i + 1, j, false, storedMap);
			else
				down = 1 + moveMaze(maze, i + 1, j, false, storedMap);
		}
	}
	else {
		if (i == maze.size() - 1 || maze[i + 1][j] == '.') {
			//Move down safely
			down = moveMaze(maze, i + 1, j, movR, storedMap);
			if (i == maze.size() - 1)
				right = moveMaze(maze, i, j + 1, true, storedMap);
			else
				right = 1 + moveMaze(maze, i, j + 1, true, storedMap);
		}
		else {
			//blocked
			down = 1 + moveMaze(maze, i + 1, j, movR, storedMap);
			if (j == maze[0].size() - 1 || maze[i][j + 1] == '.')
				right = moveMaze(maze, i, j + 1, true, storedMap);
			else
				right = 1 + moveMaze(maze, i, j + 1, true, storedMap);
		}
	}
	int result = legalMin(down, right);
	storedMap[i][j].setVal(movR, result);
	return result;
}
int main() {
	int N, M;
	cin >> N >> M;
	vector<string> maze;
	for (int i = 0; i<N; i++) {
		string str;
		cin >> str;
		maze.push_back(str);
	}
	vector<vector<St>> storedMap;
	for (int i = 0; i < N; i++) {
		vector<St> line;
		for (int j = 0; j < M; j++) {
			line.push_back(St());
		}
		storedMap.push_back(line);
	}
	int result;
	if(maze[0][0] == '.')
		result = moveMaze(maze, 0, 0, true, storedMap);
	else //[0][0]就是'b'
		result = 1 + moveMaze(maze, 0, 0, true, storedMap);
	cout << result << endl;
	return 0;
}

 

Categories
不学无术

Longest Common Subsequence 最长公共子序列

这是个很老的问题了,最近总是碰到,大三时候学的算法好多都忘记了,不如重新学一遍。
比起XXblog上的教程,我最喜欢的还是这个版本:http://www.ics.uci.edu/~eppstein/161/960229.html
里面的代码干净利落,解释也非常的清晰,一步一步怎么来的都很好。
这个问题是个动态规划的问题,最基本的样子是这样的:

假设当前搜索到字符串A,B中的字符a[i],b[j].
LCS[i,j]表示字串A,B分别在位置[i][j]及以后的最大子序列数量
IF A[i] == B[j]:
    LCS[i,j] = 1 + LCS[i+1][j+1] # 当前的算入,然后A,B再往后挪一格
ELSE
    LCS[i,j] = max(LCS[i][j+1], LCS[i+1][j]) # 分别挪动A,B后一格看看哪个好
ENDIF

由于这样递归能弄出的子问题里面,好多是重复的,因此我们可以像计算斐波那契数列那样,采用自底向上的方法,用非递归的方式实现。即从字符的末尾往前推导LCS[i][j],这样当计算LCS[i][j]的时候, LCS[i+1][j]或LCS[i][j+1]已经计算好了。
具体还是看英文教程吧,这里给出C++实现的代码。因为下午例会还得讲PPT,下面的代码用了全局变量传递计算好的序列,有点丑。

#include <iostream>
using namespace std;
/**
 * Longest Common Subsequence
 */
int** storeMat;
int max(int x, int y){
    return x>y? x:y;
}
int getLCSLength(char* A, char* B){
    int sz_A = strlen(A);
    int sz_B = strlen(B);
    storeMat = new int*[sz_A+1];
    for(int i=0; i<=sz_A; i++){
        storeMat[i] = new int[sz_B+1];  // Allocate memory
    }
    for(int i=sz_A; i>=0; i--){
        for(int j=sz_B; j>=0; j--){
            if(A[i] == '\0' || B[j] == '\0'){
                storeMat[i][j] = 0;
            } else if (A[i] == B[j]) {
                storeMat[i][j] = 1 + storeMat[1+i][1+j];
            } else {
                storeMat[i][j] = max(storeMat[1+i][j], storeMat[i][1+j]);
            }
        }
    }
    return storeMat[0][0];
}
string getLCS(char* A, char* B){
    if(NULL == storeMat){
        getLCSLength(A, B);
    }
    int i=0, j=0;
    string result;
    int sz_A = strlen(A); int sz_B = strlen(B);
    while(i < sz_A && j<sz_B){
        if(A[i] == B[j]){
            result += A[i];
            i++;
            j++;
        } else if (storeMat[i+1][j] > storeMat[i][j+1]){
            i++;
        } else {
            j++;
        }
    }
    return result;
}
int main() {
    char a[50], b[50];
    scanf("%s %s", a, b);
    cout << getLCS(a, b);
    return 0;
}

 

Categories
不学无术

LeetCode 239. Sliding Window Maximum

题目

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

Therefore, return the max sliding window as [3,3,5,5,6,7].

思路

又是《剑指Offer》的原题(#65),大致讲一下思路吧。
思路来源于之前做过的一道题,是要求设计一个栈,可以用O(1)时间找到栈中最大(最小)元素的。当时的方法是,另辟一个栈,记录可能会改变最大值的那些值(可以称他们为关键值),当这弹出的栈顶元素恰好是这些值的时候,把关键值栈顶的元素也弹出。这就相当于做了“撤销”操作,这时关键值栈顶的新元素也就是目前的最大值了。
假设我们用一个容器存放那些可能成为最大值的元素,那维护它过程就发生在向后移动窗口读入一个新元素a[i]时,主要的想法有两步:

  • 如果a[i]比容器中那些排在它前面的(先前读入的)元素大,由于a[i]的存活时间肯定比老一辈们来的长,因此前面那些比它的值还小的元素就可以删去了;
  • 删去那些“过气”的元素,也就是目前已经不在窗口中的那些元素;

每挪动一下,可以得知当前窗口最大的元素肯定就是在容器最前头的(可能是还没“过气”老大),后面排着一串老大挂掉后想当老大的元素
由于按照时间顺序,“过气”元素在容器的开头,而我们又要从容器的末尾开始找那些比a[i]小的元素,因此有这么两个重要决定:

  1. 使用双端队列存放候选元素:因为我们要方便地操作数组的首末项;
  2. 队列中存放元素的序号而不是值:这是因为我们要判断哪些元素已经滑出窗口了,如果存储值的话,每次还得重新到原序列里找位置;

整个方法的时间复杂度是O(n),空间复杂度应该也是O(n)

代码

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int numSz = nums.size();
        vector<int> results;
        if(numSz <= 0 || k > numSz){
            return results;
        }
        deque<int> working;
        //Init deque
        for(int i=0; i<k; i++){
            while(!working.empty() && nums[i]>=nums[working.back()]){
                working.pop_back();
            }
            working.push_back(i);
        }
        results.push_back(nums[working.front()]);
        //Interate through remaining nums
        for(int i=k; i<numSz; i++){
            while(!working.empty() && working.front() <= (i-k)){
                working.pop_front(); //Remove the out-of-window elements
            }
            while(!working.empty() && (nums[i]>=nums[working.back()])){
                working.pop_back();  //Remove the smaller elements
            }
            working.push_back(i);
            results.push_back(nums[working.front()]);
        }
        return results;
    }
};

 

Categories
不学无术

LeetCode 335. Self Crossing

题目:

You are given an array x of n positive numbers. You start at point (0,0) and moves x[0] metres to the north, then x[1] metres to the west, x[2] metres to the south,x[3] metres to the east and so on. In other words, after each move your direction changes counter-clockwise.
Write a one-pass algorithm with O(1) extra space to determine, if your path crosses itself, or not.
Example 1:

Given x = [2, 1, 1, 2],
┌───┐
│   │
└───┼──>
    │
Return true (self crossing)

Example 2:

Given x = [1, 2, 3, 4],
┌──────┐
│      │
│
│
└────────────>
Return false (not self crossing)

Example 3:

Given x = [1, 1, 1, 1],
┌───┐
│   │
└───┼>
Return true (self crossing)

思路:

https://leetcode.com/discuss/88054/java-oms-with-explanation
本人愚钝,想不到O(1)空间复杂度的方法,只能看Discussion啦,看完以后发现,遇到问题能够分析出各种情况真是不容易。
把大神的代码总结一下,可以变成下面这张图,图中给出了可能发生self-crossing的所有情况(其他情况都是这些情况的”旋转”版本):
LeetCode_Self_Crossing
所以就是遍历所有移动步x[i],判断之前的情况是否在这三种情况之内,如果有的话,那肯定就有crossing了。其中情况2的意思是,当前的步进x[i]覆盖到了x[i-4]了,其他情况看图应该都好理解的。

代码:

class Solution {
public:
    bool isSelfCrossing(vector<int>& x) {
        int numSz = x.size();
        if(numSz <= 3)
            return false;
        for(int i=3; i<numSz; i++){
            if(x[i]>=x[i-2] && x[i-1]<=x[i-3]) //Cond 1
                return true;
            else if(i>=4 && x[i-1]==x[i-3] && x[i]+x[i-4]>=x[i-2])//Cond 2
                return true;
            else if(i>=5 && x[i-1]<=x[i-3] && x[i-4]<=x[i-2] && x[i]+x[i-4]>=x[i-2] && x[i-1]+x[i-5]>=x[i-3]) //Cond 3
                return true;
        }
        return false;
    }
};