【动态规划】【C++算法】LeetCoce996正方形数组的数目

简介: 【动态规划】【C++算法】LeetCoce996正方形数组的数目

作者推荐

动态规划】【前缀和】【C++算法】LCP 57. 打地鼠

本文涉及知识点

动态规划汇总

LeetCoce996正方形数组的数目

给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。

返回 A 的正方形排列的数目。两个排列 A1 和 A2 不同的充要条件是存在某个索引 i,使得 A1[i] != A2[i]。

示例 1:

输入:[1,17,8]

输出:2

解释:

[1,8,17] 和 [17,8,1] 都是有效的排列。

示例 2:

输入:[2,2,2]

输出:1

提示:

1 <= A.length <= 12

0 <= A[i] <= 1e9

动态规划

动态规划的状态表示

mask表示那些数字已经选取,end表示结束数字下标。 空间复杂度: O( (1 << 12) *12)。

dp[mask][end]记录符合条件的数量。

动态规划的转移方程

vNext记录所有能和end组成完全平方数的数字下标。

f o r n e x t : v N e x t { n e x t 已经处理忽略 ( 1 < < n e x t ) m a s k d p [ m a s k ∣ ( 1 < < n e x t ) ] [ n e x t ] + = d p [ m a s k ] [ e n d ] e l s e for \quad next:vNext \begin{cases} next已经处理忽略 & (1 << next) & mask \\ dp[mask|(1<<next)][next] += dp[mask][end] & else \\ \end{cases}fornext:vNext{next已经处理忽略dp[mask(1<<next)][next]+=dp[mask][end](1<<next)elsemask

动态规矩的初始状态

for i = 0 : n-1 dp[1 << 1][i] = 1

动态规划的填表顺序

mask|(1<<next) 一定大于mask,更按mask从小到处理可以保证无后效性。

动态规划的返回值

dp.back() 之和

关于去重

一个数出现n次,则除以n! 。就是n的排列数。注意:12! 在int32表示范围中。

变量解释

setQ 记录小于等于2e9的

代码

核心代码

class Solution {
public:
  int numSquarefulPerms(vector<int>& nums) {
    m_c = nums.size();
    m_iMaskCount = 1 << m_c;
    unordered_set<int> setQ;
    for (int i = 0; i * i <= 2'000'000'000; i++)
    {
      setQ.emplace(i * i);
    }
    vector<vector<int>> vNext(m_c);
    for (int i = 0; i < m_c; i++)
    {
      for (int j = 0; j < i; j++)
      {
        if (setQ.count(nums[i] + nums[j]))
        {
          vNext[i].emplace_back(j);
          vNext[j].emplace_back(i);
        }
      }
    }
    vector<vector<int>> dp(m_iMaskCount, vector<int>(m_c));
    for (int i = 0; i < m_c; i++)
    {
      dp[1 << i][i] = 1;
    }
    for (int mask = 0; mask < m_iMaskCount; mask++)
    {
      for (int end = 0; end < m_c; end++)
      {
        for (const auto& next : vNext[end])
        {
          if (mask & (1 << next))
          {// next已经处理
            continue;
          }
          dp[mask | (1 << next)][next] += dp[mask][end];
        }
      }
    }
    int iCnt = std::accumulate(dp.back().begin(), dp.back().end(), 0);
    std::unordered_map<int, int> mNumCount;
    for (const auto& n : nums)
    {
      mNumCount[n]++;
    }
    for (const auto& [tmp, cnt] : mNumCount)
    {
      for (int j = 1; j <= cnt; j++)
      {
        iCnt /= j;
      }
    }
    return iCnt;
  }
  int m_c,m_iMaskCount;
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<int> nums;
  {
    Solution sln;
    nums = { 0,0,1 };
    auto res = sln.numSquarefulPerms(nums);
    Assert(res, 3);
  }
  {
    Solution sln;
    nums = { 0,0,0,1,1,1 };
    auto res = sln.numSquarefulPerms(nums);
    Assert(res, 4);
  }
  {
    Solution sln;
    nums = { 1,17,8 };
    auto res = sln.numSquarefulPerms(nums);
    Assert(res, 2);
  }
  {
    Solution sln;
    nums = { 2,2,2 };
    auto res = sln.numSquarefulPerms(nums);
    Assert(res, 1);
  }
  
  
}


相关文章
|
3天前
|
存储 人工智能 C++
【重学C++】【指针】详解让人迷茫的指针数组和数组指针
【重学C++】【指针】详解让人迷茫的指针数组和数组指针
25 1
|
8天前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
30 1
|
14天前
|
算法
算法系列--动态规划--背包问题(5)--二维费用背包问题(上)
算法系列--动态规划--背包问题(5)--二维费用背包问题(上)
19 0
|
14天前
|
算法
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
19 0
|
14天前
|
算法
算法系列--动态规划--背包问题(3)--完全背包介绍(下)
算法系列--动态规划--背包问题(3)--完全背包介绍(下)
16 0
|
14天前
|
算法
算法系列--两个数组的dp问题(2)(下)
算法系列--两个数组的dp问题(2)(下)
19 0
|
14天前
|
存储 算法
算法系列--动态规划--⼦数组、⼦串系列(数组中连续的⼀段)(1)(下)
算法系列--动态规划--⼦数组、⼦串系列(数组中连续的⼀段)(1)
17 0
|
14天前
|
算法
算法系列--动态规划--⼦数组、⼦串系列(数组中连续的⼀段)(1)(上)
算法系列--动态规划--⼦数组、⼦串系列(数组中连续的⼀段)(1)
21 0
|
14天前
|
算法 计算机视觉
算法系列--两个数组的dp问题(1)(下)
算法系列--两个数组的dp问题(1)
18 0
|
14天前
|
算法
算法系列--两个数组的dp问题(1)(上)
算法系列--两个数组的dp问题(1)
14 0