春招刷题笔记-剑指offer-算法和数据操作
算法和数据操作——动态规划、贪心算法与位运算
动态规划与贪心算法
动态规划有以下几个特点:
一、如果是求一个问题的最优解,比如最大值或者最小值,并且问题可以分解为更小的重叠的子问题来解决;
二、整个问题的最优解是依赖于子问题的最优解的问题,一般使用动态规划来解决;
三、把大问题分解成若干个小问题,这些小问题之间还有相互重叠的更小的子问题;
四、由于子问题在分解大问题的过程中重复出现,为了避免重复计算子问题,我们可以用从下往上的顺序先计算出子问题的最优解并且保存下来,再以此为基础求取大问题的最优解。从上往下分析问题,从下往上求解问题。
剪绳子
题目描述:给你一根长度为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。
// 附带上测试的程序
#include <iostream>
#include <cmath>
using namespace std;
// ===================动态规划=====================
int maxProductAfterCutting_solution1(int length) {
// 动态规划就是以空间换取时间,将过程中可能会需要的中间
// 数据全部计算出来并且保存下来,计算到最终的值就是解。
if (length < 2)
return 0;
if (length == 2)
return 1;
if (length == 3)
return 2;
int* maxpro = new int[length + 1];
for (int i = 0; i < length + 1; i++)
maxpro[i] = 0;
// 这里的maxPro[2] = 2, 表明
maxpro[0] = 0;
maxpro[1] = 1;
maxpro[2] = 2;
maxpro[3] = 3;
int max = 0;
for (int i = 4; i <= length; i++) {
max = 0;
for (int j = 1; j <= i / 2; j++) {
int temp = maxpro[j] * maxpro[i - j];
if (max < temp)
max = temp;
}
maxpro[i] = max;
}
int res = maxpro[length];
delete[] maxpro;
return res;
}
// ==================贪心算法=====================
int maxProductAfterCutting_solution2(int length) {
if (length < 2)
return 0;
if (length == 2)
return 1;
if (length == 3)
return 2;
int timesof3 = length / 3;
if (length - 3 * timesof3 == 1)
timesof3 -= 1;
int timesof2 = (length - 3 * timesof3) / 2;
return (int) (pow(3, timesof3)) * (int)(pow(2, timesof2));
}
// ====================测试代码====================
void test(const char* testName, int length, int expected)
{
int result1 = maxProductAfterCutting_solution1(length);
if(result1 == expected)
std::cout << "Solution1 for " << testName << " passed." << std::endl;
else {
std::cout << "Solution1 for " << testName << " FAILED. ";
cout << "length: " << length << " expected: " << expected << " result: " << result1 << std::endl;
}
int result2 = maxProductAfterCutting_solution2(length);
if(result2 == expected)
std::cout << "Solution2 for " << testName << " passed." << std::endl;
else
std::cout << "Solution2 for " << testName << " FAILED." << std::endl;
}
void test1()
{
int length = 1;
int expected = 0;
test("test1", length, expected);
}
void test2()
{
int length = 2;
int expected = 1;
test("test2", length, expected);
}
void test3()
{
int length = 3;
int expected = 2;
test("test3", length, expected);
}
void test4()
{
int length = 4;
int expected = 4;
test("test4", length, expected);
}
void test5()
{
int length = 5;
int expected = 6;
test("test5", length, expected);
}
void test6()
{
int length = 6;
int expected = 9;
test("test6", length, expected);
}
void test7()
{
int length = 7;
int expected = 12;
test("test7", length, expected);
}
void test8()
{
int length = 8;
int expected = 18;
test("test8", length, expected);
}
void test9()
{
int length = 9;
int expected = 27;
test("test9", length, expected);
}
void test10()
{
int length = 10;
int expected = 36;
test("test10", length, expected);
}
void test11()
{
int length = 50;
int expected = 86093442;
test("test11", length, expected);
}
int main(int agrc, char* argv[])
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
test9();
test10();
test11();
return 0;
}
位运算
计算二进制中1的个数
题目描述:
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution {
public:
int NumberOf1(int n) {
// 首先把N与1做位与的运算可以判定n的最后一位是不是1,接着把
// 1向左移一位得到,再和n做位于运算,可以判定n的倒数第二位是不是1,
// 接着再将2左移一位得到4,判定n的倒数第三位是不是1,依次计算下去,
// 每一次的循环可以判数字的一个位是不是1。整数的二进制有多少位,
// 就会进行多少次循环。
int countof1 = 0;
unsigned int flag = 1;
while (flag) {
if (n & flag)
++countof1;
flag = flag << 1;
}
return countof1;
}
};
// 一种比较惊喜的解决办法
class Solution {
public:
int NumberOf1(int n) {
/*
在数字的二进制表示中,如果把一个数减去1,那么,这个二进制数的
最右边的1,会变成0,如果这个1后面有0,那么这个1后面的所有0都会
编程1,1之前的数字保持不变。假如数字是12的二进制数1100,那么1100
减去1会变成1011,,此时,如果把1100和1011做位与的运算1100&1011=1000
相当于把1100的最右边的1变成了0,所以如果要计算二进制数中的1的个数,
可以采用将数字减去1,并且和原数字做位与运算的方式来计算二进制中有多少
个1.以下的部分是程序:
*/
int countof1 = 0;
while (n) {
countof1++;
n = (n - 1) & n;
}
return countof1;
}
};
判断一个数是否是2的整数次方
bool PowerOf2(int n) {
/*
在数字的二进制表示中,如果把一个数减去1,那么,这个二进制数的
最右边的1,会变成0,如果这个1后面有0,那么这个1后面的所有0都会
编程1,1之前的数字保持不变。假如数字是12的二进制数1100,那么1100
减去1会变成1011,,此时,如果把1100和1011做位与的运算1100&1011=1000
相当于把1100的最右边的1变成了0,所以如果要计算二进制数中的1的个数,
可以采用将数字减去1,并且和原数字做位与运算的方式来计算二进制中有多少
个1.
那么要计算一个数是不是2的整数次方,只需要判断这个数的二进制表示中是不是只有1个1
也即如果只需要计算一次上述的运算,那么这个整数就是2的整数次方,如果需要进行1次以
上的运算,那么这个整数就不是2的整数次方。
*/
if (n <= 0) return false;
n = (n - 1) & n;
if (n == 0)
return true;
return false;
}
计算整数m的二进制需要改变多少位才能得到整数n的二进制
思路分析: 首先进行m与n的异或运算,再统计异或运算得到的1的个数,即为m的二进制改一定位数得到n的二进制。
int NumberNeededChannged(int m, int n) {
int temp = m ^ n;
int countof1 = 0;
while (temp) {
// 计算temp中1的个数
++countof1;
temp = (temp - 1) & temp;
}
return countof1;
}