Travis's Tech Blog

Noting Travis's daily life


剑指Offer笔记

面试题二

实现一个singleton模式,设计一个类,我们只能生成该类的一个实例。

Solution1

 public sealed class Singleton1
{
    private Singleton1()
    {
    }

    private static Singleton1 instance = null;
    public static Singleton1 Instance
    {
        get
        {
            if (instance == null)
                instance = new Singleton1();

            return instance;
        }
    }
}

只适用于单线程环境,倘若两个线程同时判断instance是否为null,当前若无instance,则会同时创建实例,则此时不符合singleton模式。

Solution2

public sealed class Singleton2
{
    private Singleton2()
    {
    }

    private static readonly object syncObj = new object();

    private static Singleton2 instance = null;
    public static Singleton2 Instance
    {
        get
        {
            lock (syncObj)
            {
                if (instance == null)
                    instance = new Singleton2();
            }

            return instance;
        }
    }
}

加锁过程太耗时,若两个同时申请加锁一个需要等待,等锁释放后却发现已经存在实例了,没有必要耗费不需要的加锁时间。

solution 3

public sealed class Singleton3
{
    private Singleton3()
    {
    }

    private static object syncObj = new object();

    private static Singleton3 instance = null;
    public static Singleton3 Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncObj)
                {
                    if (instance == null)
                        instance = new Singleton3();
                }
            }

            return instance;
        }
    }
}

解决solution2的问题,只在第一次试图创建实例的时候需要加锁,确保多线程环境下也只创建一个实例。

solution 4

使用静态构造函数

 public sealed class Singleton4
{
    private Singleton4()
    {
        Console.WriteLine("An instance of Singleton4 is created.");
    }

    public static void Print()
    {
        Console.WriteLine("Singleton4 Print");
    }

    private static Singleton4 instance = new Singleton4();
    //静态构造方法,在第一次使用Singleton4的时候就调用,先初始化静态变量,
    //再调用静态构造方法,然后如果有构造方法 在调用初始化非静态变量和执行构造方法
    public static Singleton4 Instance
    {
        get
        {
            return instance;
        }
    }
}

静态构造函数,只在第一次调用的时候调用一次。在Singleton4中,实例instance并不是在第一次调用属性Singleton4.Instance的时候被创建,而是在第一次使用到Singleton4就会被创建。若在Singleton4中有一个静态方法不需要创建实例,但在调用该方法前会提前创建Instance,则会降低内存的使用效率。

Solution 5 Best solution

public sealed class Singleton5
{
    Singleton5()
    {
        Console.WriteLine("An instance of Singleton5 is created.");
    }

    public static void Print()
    {
        Console.WriteLine("Singleton5 Print");
    }

    public static Singleton5 Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        static Nested()
        {
        }

        internal static readonly Singleton5 instance = new Singleton5();
    }
}	 在内部定义一个私有类型Nested,只有在Nested被调用的时候才会使用静态构造方法生成instance。而Nested仅在Singleton5的Instance中被调用。因此,当调用不需要使用Singleton5的instance的时候,就不会触发.Net运行时调用Nested,则不会创建实例,做到按需创建。

面试题三

NOTE:在C/C++中,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。

example:

#include<iostream>
using namespace std;
int GetSize(int data[]) {
	return sizeof(data);
}

int main() {
	int data1[] = { 1,2,3,4,5 };
	int size1 = sizeof(data1);

	int* data2 = data1;
	int size2 = sizeof(data2);
	int size3 = GetSize(data1);
	cout << size1 << " " << size2 << " " << size3 << endl;
}

输出结果为20 4 4,size1求得是数组的大小,5个整数,每个整数占4个字节,因此占20个字节,size2,求的是int类型指针的大小,在32位系统上,对任意指针求sizeof都是4个字节。在C/C++中,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。因此,尽管GetSize的参数data为数组,但它会退化为指针。

面试题3(一):找出数组中重复的数字

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了, 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3}, 那么对应的输出是重复的数字2或者3。

若用排序做,则时间复杂度为O(nlogn)。

若使用哈希表做,则时间复杂度为O(1),空间复杂度为O(n)。从头到尾扫描数组,若哈希表中不存在该元素,则将该元素添加至哈希表中,否则找到一个重复数字。

若存在重复数字,输出true,并将重复数字赋给duplication(引用)。总的时间复杂度为O(n),空间复杂度为O(1).

bool duplicate(int numbers[], int length, int* duplication)
{
	if (numbers == nullptr || length <= 0)
		return false;

	for (int i = 0; i < length; ++i)
	{
		if (numbers[i] < 0 || numbers[i] > length - 1)
			return false;
	}

	for (int i = 0; i < length; ++i)
	{
		while (numbers[i] != i)
		{
			if (numbers[i] == numbers[numbers[i]])
			{
				*duplication = numbers[i];
				return true;
			}

			// 交换numbers[i]和numbers[numbers[i]]             
			int temp = numbers[i];
			numbers[i] = numbers[temp];
			numbers[temp] = temp;
		}
	}

	return false;
}

面试题3(二):不修改数组找出重复的数字

题目:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。

使用类似二分查找的思想,每次将数组以中间数为界限分成两部分,例如长度为8的数组中最大为7,则以4作为分界数,扫描一次,比4大的分一堆,比四小的分一堆,查看哪一堆的数字数大于3,则确定重复数字在哪一堆,在进行递归。

int countRange(const int* numbers, int length, int start, int end);

// 参数:
//        numbers:     一个整数数组
//        length:      数组的长度
// 返回值:             
//        正数  - 输入有效,并且数组中存在重复的数字,返回值为重复的数字
//        负数  - 输入无效,或者数组中没有重复的数字
int getDuplication(const int* numbers, int length)
{
    if(numbers == nullptr || length <= 0)
        return -1;

    int start = 1;
    int end = length - 1;
    while(end >= start)
    {
        int middle = ((end - start) >> 1) + start;
        int count = countRange(numbers, length, start, middle);
        if(end == start)
        {
            if(count > 1)
                return start;
            else
                break;
        }

        if(count > (middle - start + 1))
            end = middle;
        else
            start = middle + 1;
    }
    return -1;
}

int countRange(const int* numbers, int length, int start, int end)
{
    if(numbers == nullptr)
        return 0;

    int count = 0;
    for(int i = 0; i < length; i++)
        if(numbers[i] >= start && numbers[i] <= end)
            ++count;
    return count;
}

面试题四

二维数组中的查找

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

每次取右上角的数字,若要寻找的数字=右上角,则找到;否则,如果比右上角数字大,则去掉当前行;如果比右上角数字小,则去掉当前列。

bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;

    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns - 1;
        while(row < rows && column >=0)
        {
            if(matrix[row * columns + column] == number)
            {
                found = true;
                break;
            }
            else if(matrix[row * columns + column] > number)
                -- column;
            else
                ++ row;
        }
    }

    return found;
}

面试题五

为了节省空间,C/C++把常量字符串放在一个单独的内存区域,当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址。但用常量内存初始化数组是,情况却又不一样。

char str1[] = "hello world";
char str2[] = "hello world";

char* str3 = "hello world";
char* str4 = "hello world";

str1==str2 -->false
str3==str4 -->true

str1和str2是两个初始地址不同的数组,因此str1和str2不同,str3和str4 是两个指针同时指向内存中hello world的地址,由于该字符串为常量字符串,因此在内存中仅有一个拷贝,str3和str4指向同一个地址。

而在C#中,String是不可变的,一旦试图改变String的内容,就会产生一个新的实例。

String str = "hello";
str.ToUpper();
str.Insert(0,"World");

str的内容不改变,改变的字符串会通过返回值通过新的String实例返回。若需要改变String的值,则使用StringBuilder。

面试题5:替换空格

题目:请实现一个函数,把字符串中的每个空格替换成”%20”。例如输入“We are happy.”,则输出“We%20are%20happy.”。

/*length 为字符数组str的总容量,大于或等于字符串str的实际长度*/
void ReplaceBlank(char str[], int length)
{
	if (str == nullptr && length <= 0)
		return;

	/*originalLength 为字符串str的实际长度*/
	int originalLength = 0;
	int numberOfBlank = 0;
	int i = 0;
	while (str[i] != '\0')
	{
		++originalLength;

		if (str[i] == ' ')
			++numberOfBlank;

		++i;
	}

	/*newLength 为把空格替换成'%20'之后的长度*/
	int newLength = originalLength + numberOfBlank * 2;
	if (newLength > length)
		return;

	int indexOfOriginal = originalLength;
	int indexOfNew = newLength;
	while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal)
	{
		if (str[indexOfOriginal] == ' ')
		{
			str[indexOfNew--] = '0';
			str[indexOfNew--] = '2';
			str[indexOfNew--] = '%';
		}
		else
		{
			str[indexOfNew--] = str[indexOfOriginal];
		}

		--indexOfOriginal;
	}
}

面试题七

题目:输入某二叉树的前序遍历和中序遍历的结构,请重建该二叉树。假设输入的前序遍历和中序遍历中都不不含重复的数字。

前序遍历第一个数字就是根节点的值,找到中序遍历中该对应值的位置,则左子树的序列在根节点左边,右子树的序列在右边。可利用递归的方法分别构建左右子树。

BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder);

BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
	if (preorder == nullptr || inorder == nullptr || length <= 0)
		return nullptr;

	return ConstructCore(preorder, preorder + length - 1,
		inorder, inorder + length - 1);
}

BinaryTreeNode* ConstructCore
(
	int* startPreorder, int* endPreorder,
	int* startInorder, int* endInorder
)
{
	// 前序遍历序列的第一个数字是根结点的值
	int rootValue = startPreorder[0];
	BinaryTreeNode* root = new BinaryTreeNode();
	root->m_nValue = rootValue;
	root->m_pLeft = root->m_pRight = nullptr;

	if (startPreorder == endPreorder)
	{
		if (startInorder == endInorder && *startPreorder == *startInorder)
			return root;
		else
			throw std::exception("Invalid input.");
	}

	// 在中序遍历中找到根结点的值
	int* rootInorder = startInorder;
	while (rootInorder <= endInorder && *rootInorder != rootValue)
		++rootInorder;

	if (rootInorder == endInorder && *rootInorder != rootValue)
		throw std::exception("Invalid input.");

	int leftLength = rootInorder - startInorder;
	int* leftPreorderEnd = startPreorder + leftLength;
	if (leftLength > 0)
	{
		// 构建左子树
		root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd,
			startInorder, rootInorder - 1);
	}
	if (leftLength < endPreorder - startPreorder)
	{
		// 构建右子树
		root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder,
			rootInorder + 1, endInorder);
	}

	return root;
}

面试题八

题目:给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。

如果一个节点由右子树,那么他的下一个节点就是他的右子树中的最左节点。也就是说,从右子节点出发一直沿着指向左子节点的指针,就能找到它的下一个节点。

若一个节点没有右子树,如果节点是他的父节点的左子节点,那么他的下一个节点就是他的父节点。

若一个节点既没有右子树,他还是他的父节点的右子节点,我们可以沿着指向父节点的指针一直向上遍历,直到找到一个是他父节点的左子树的节点,如果这样的节点存在,那么这个节点的父节点,就是我们要找的下一个节点。

BinaryTreeNode* GetNext(BinaryTreeNode* pNode)
{
	if (pNode == nullptr)
		return nullptr;

	BinaryTreeNode* pNext = nullptr;
	if (pNode->m_pRight != nullptr)
	{
		BinaryTreeNode* pRight = pNode->m_pRight;
		while (pRight->m_pLeft != nullptr)
			pRight = pRight->m_pLeft;

		pNext = pRight;
	}
	else if (pNode->m_pParent != nullptr)
	{
		BinaryTreeNode* pCurrent = pNode;
		BinaryTreeNode* pParent = pNode->m_pParent;
		while (pParent != nullptr && pCurrent == pParent->m_pRight)
		{
			pCurrent = pParent;
			pParent = pParent->m_pParent;
		}

		pNext = pParent;
	}

	return pNext;
}

面试题九

用两个栈实现队列

题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。

思路如下:

添加到尾部时,只需要将其push到stack1中;

若需要在头部删除节点的时候,若stack2为空,则将stack1中的元素全部push到stack2,因为元素在添加到stack1时是先进后出,因此push到stack2后又将变成先进先出(顺序会和stack1调转),此后在使用stack2.top弹出头部元素即可实现队列的先进先出。

/**
往队列尾部添加元素
*/
template<typename T>
void CQueue::appendTail(const T& node) {
	if (stack1 == nullptr)
		stack1 = new stack<T>();
	if (stack2 == nullptr)
		stack2 = new stack<T>();
	stack1.push(node);
}
/**
删除队列的头元素,并将其返回
*/
template<typename T>
T CQueue::deleteHead() {
	if (stack2.size() <= 0)
	{
		while (stack1.size()>0)
		{
			T& data = stack1.top();
			stack1.pop();
			stack2.push(data);
		}
	}

	if (stack2.size() == 0)
		throw new exception("queue is empty");

	T head = stack2.top();
	stack2.pop();

	return head;
}

关联题目:用两个队列实现一个栈

要实现栈的先进后出,则先将数据存在queue1,若需要弹栈操作,则将queue1中除了最后一个元素以外所有数据都先进先出退出队列,存到queue2中,然后将最后一个元素弹出返回。若下次再要压栈操作,则将数据存在queue2。两个栈轮流使用存放数据。

template<typename T> T CStack<T>::popStack() {
	if (flag == 0) {
		if (queue1.size() == 1) { 
			T data = queue1.front();  
			queue1.pop(); 
			return data;
		}
		//数据在queue1中
		if (queue1.size() == 0) throw new exception("stack is empty!");
		while (queue1.size() > 1) {
			T data = queue1.front();
			queue1.pop();
			queue2.push(data);
		}

		flag = 1;
		T result = queue1.front();
		queue1.pop();
		return result;
	}
	else {
		if (queue2.size() == 1) {
			T data = queue2.front();
			queue2.pop();
			return data;
		}
		if (queue2.size() == 0) throw new exception("stack is empty!");
		while (queue2.size() > 1) {
			T data = queue2.front();
			queue2.pop();
			queue1.push(data);
		}

		flag = 0;
		T result = queue2.front();
		queue2.pop();
		return result;
	}
}

template<typename T> void CStack<T>::pushStack(const T& node) {
	if (flag == 1) {
		queue2.push(node);
	}
	else {
		queue1.push(node);
	}
}

算法和数据操作

如果面试题要求在二维数组上搜索路径,那么可以尝试用回溯法。回溯法很适合使用递归的代码实现,当不可使用递归的时候可以使用栈来模拟递归的过程。

若是求某个问题的最优解且可以分成多个子问题,则可以尝试动态规划,为避免不必要的重复计算,使用自下而上的的循环代码来实现,也就是把子问题的最优解先算出来再用数组保存下来,接下来基于子问题的解求解大问题的解。

若有提醒说在分解子问题的时候是否存在某个特殊的选择,如果采用这个选择将一定能得到最优解,那么通常意味着这个面试题可以适用于贪婪算法。

各种排序算法比较

1 快速排序(QuickSort)

快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。

(1) 如果不多于1个数据,直接返回。

(2) 一般选择序列最左边的值作为支点数据。

(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。

(4) 对两边利用递归排序数列。

快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。

2 归并排序(MergeSort)

归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。

3 堆排序(HeapSort)

堆排序适合于数据量非常大的场合(百万数据)。

堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。

堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。

4 Shell排序(ShellSort)

Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。

Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。

5 插入排序(InsertSort)

插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。

6 冒泡排序(BubbleSort)

冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。

7 交换排序(ExchangeSort)和选择排序(SelectSort)

这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。

8 基数排序(RadixSort)

基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。

(转载自(http://flyingbread.cnblogs.com/))

回溯法

回溯法可以看作是brutal force的升级版,可以将解决问题的所有选项看成是一个树状结构。从根节点到叶节点的一条路径可以看作是问题的一个解,若路径满足题目要求,则找到一个解决方案。若在叶节点不满足条件,我们需要回溯到它的上一个节点寻找其他选项,若该节点所有选项都尝试过了,则在回到上一节点。若所有路径都不满足,则问题无解。

面试题13:机器人的运动范围##

题目:地上有一个m行n列的方格。一个机器人从坐标(0, 0)的格子开始移动,它每一次可以向左、右、上、下移动一格,但不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35, 37),因为3+5+3+7=18。但它不能进入方格(35, 38),因为3+5+3+8=19。请问该机器人能够到达多少个格子?

int movingCountCore(int threshold, int rows, int cols, int row, int col, bool* visited);
bool check(int threshold, int rows, int cols, int row, int col, bool* visited);
int getDigitSum(int number);

int movingCount(int threshold, int rows, int cols)
{
	if (threshold < 0 || rows <= 0 || cols <= 0)
		return 0;

	bool *visited = new bool[rows * cols];
	for (int i = 0; i < rows * cols; ++i)
		visited[i] = false;

	int count = movingCountCore(threshold, rows, cols,
		0, 0, visited);

	delete[] visited;

	return count;
}

int movingCountCore(int threshold, int rows, int cols, int row,
	int col, bool* visited)
{
	int count = 0;
	if (check(threshold, rows, cols, row, col, visited))
	{
		visited[row * cols + col] = true;

		count = 1 + movingCountCore(threshold, rows, cols,
			row - 1, col, visited)
			+ movingCountCore(threshold, rows, cols,
				row, col - 1, visited)
			+ movingCountCore(threshold, rows, cols,
				row + 1, col, visited)
			+ movingCountCore(threshold, rows, cols,
				row, col + 1, visited);
	}

	return count;
}
//判断能否进入row,col的格子
bool check(int threshold, int rows, int cols, int row, int col,
	bool* visited)
{
	if (row >= 0 && row < rows && col >= 0 && col < cols
		&& getDigitSum(row) + getDigitSum(col) <= threshold
		&& !visited[row* cols + col])
		return true;

	return false;
}

int getDigitSum(int number)
{
	int sum = 0;
	while (number > 0)
	{
		sum += number % 10;
		number /= 10;
	}

	return sum;
}

动态规划以及贪心算法

若我们需要求一个问题的最优解,通常可以将其分解成若干个子问题,且子问题也可能有最优解。如果我们可以将小问题的最优解组合起来得到整个大问题的最优解。那么就可以使用动态规划来解决。为避免不必要的重复计算,使用自下而上的的循环代码来实现,也就是把子问题的最优解先算出来再用数组保存下来,接下来基于子问题的解求解大问题的解。

面试题14:剪绳子

题目:给你一根长度为n绳子,请把绳子剪成m段(m、n都是整数,n>1并且m≥1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1]…*k[m]可能的最大乘积是多少?例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

我们使用一个长度为length+1的int数组存储长度为i的最优解。对于每一个子问题,我们通过循环求解可能存在的每一种情况,并将最大值保存在production数组中。

int maxProductAfterCutting_dynamicProgramming(int length) {
	if (length < 2)
		return 0;
	if (length == 2)
		return 1;
	if (length == 3)
		return 2;

	int *production = new int[length + 1];
	production[0] = 0;
	production[1] = 1;
	production[2] = 2;
	production[3] = 3;

	for (int i = 4; i < length + 1; i++) {
		int max = 0;
		//循环求解子问题的最优解
		for (int j = 1; j <= i / 2; j++) {
			int temp = production[i - j] * production[j];
			max = max > temp ? max : temp;
		}
		production[i] = max;
	}
	int max = production[length];
	delete[] production;
	return max;
}

贪婪算法

在用贪心算法解决问题的时候,每一步都可以做出一个贪婪的选择,基于此选择,我们可以确定能够得到最优解。

在这个问题中,我们的贪心策略是尽可能多地剪长度为3的线段。

int maxProductAfterCutting_greedy(int length)
{
	if (length < 2)
		return 0;
	if (length == 2)
		return 1;
	if (length == 3)
		return 2;

	// 尽可能多地减去长度为3的绳子段
	int timesOf3 = length / 3;

	// 当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段。
	// 此时更好的方法是把绳子剪成长度为2的两段,因为2*2 > 3*1。
	if (length - timesOf3 * 3 == 1)
		timesOf3 -= 1;

	int timesOf2 = (length - timesOf3 * 3) / 2;

	return (int)(pow(3, timesOf3)) * (int)(pow(2, timesOf2));
}

位运算

在右移运算符处理有符号数时,需要用数字的符号位填补原来的n位。

例如: 1000110»2=1110001

面试题15:二进制中的1

题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。

可能存在死循环的算法

int NumberOf1_wrongSolution(int n) {
	int count = 0;
	while (n) {
		if (n & 1)
			count++;
		n = n >> 1;
	}
	return count;
}

如果输入的为一个负数,例如0x800000,若右移一位,并不是0x400000,而是0xC00000,若不断右移,则会变成0xFFFFFF,陷入死循环。

为避免死循环,我们可以采用以下思路:

int NumberOf1_Solution1(int n)
{
	int count = 0;
	unsigned int flag = 1;
	while (flag)
	{
		if (n & flag)
			count++;

		flag = flag << 1;
	}

	return count;
}

提高效率的话:可以发现把一个整数减去1,在和原来的整数做与运算,会把该整数最右边的1变为0.那么有多少个1就会执行多少次。

int NumberOf1_Solution2(int n)
{
	int count = 0;

	while (n)
	{
		++count;
		n = (n - 1) & n;
	}

	return count;
}